Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / C#
Article

Drag and Drop Tab Control

Rate me:
Please Sign up or sign in to vote.
4.70/5 (20 votes)
15 Jun 20022 min read 267K   5.8K   78   30
Reorder TabPages in a TabControl through drag and drop.

Sample Image

Introduction

Having a need for my end-users to be able to reorder a set of tabs through drag and drop functionality, I set off on a search to find a control that exhibited this behavior. In fact, I, myself, frequently use this feature within the Visual Studio.NET IDE to better control my documents. Since this ability was not "baked in" to the .NET TabControl I took it upon myself to create the control from scratch (or at least extending TabControl). The end product of this endeavor is the DraggableTabControl

After deriving a class from TabControl, the only tricky part, I found, was determining which TabPage I was hovering over in the DragOver event. When a user drags over the actual tabs of a TabControl, the

TabControl
itself actually gets the event, not the TabPage To overcome this, I wrote a little function to determine if the point at which the mouse is being dragged is contained within the rectangle of each tab. See the function FindTabPageByTab below.

private TabPage GetTabPageByTab(Point pt)
{
    TabPage tp = null;

    //Loop over the tab pages, using GetTabRect() to determine
    //if that pages Tab contains the point we passed in. 
    for(int i = 0; i < TabPages.Count; i++)
    {
        if(GetTabRect(i).Contains(pt))
        {
            //We found the tab page, no need to go on...
            tp = TabPages[i];
            break;
        }
    }
    
    return tp;
}

To accomplish the TabPage reordering I first place all of the tabs except the one being dragged into an array and then insert the one I am dragging into the array at the proper point. I then clear all TabPages and add my array back in to the TabControl. It is important, however, that we do this as infrequently as possible to avoid flicker, etc. As a result there are quite a few checks in the code. See the code snippet below to see whay I am talking about...

protected override void OnDragOver(System.Windows.Forms.DragEventArgs e)
{
    base.OnDragOver(e);

    Point pt = new Point(e.X, e.Y);
    //We need client coordinates.
    pt = PointToClient(pt);
    
    //Get the tab we are hovering over.
    TabPage hover_tab = GetTabPageByTab(pt);

    //Make sure we are on a tab.
    if(hover_tab != null)
    {
        //Make sure there is a TabPage being dragged.
        if(e.Data.GetDataPresent(typeof(TabPage)))
        {
            e.Effect = DragDropEffects.Move;
            TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));

            int item_drag_index = FindIndex(drag_tab);
            int drop_location_index = FindIndex(hover_tab);

            //Don't do anything if we are hovering over ourself.
            if(item_drag_index != drop_location_index)
            {
                ArrayList pages = new ArrayList();
                
                //Put all tab pages into an array.
                for(int i = 0; i < TabPages.Count; i++)
                {
                    //Except the one we are dragging.
                    if(i != item_drag_index)
                        pages.Add(TabPages[i]);
                }

                //Now put the one we are dragging it at the proper location.
                pages.Insert(drop_location_index, drag_tab);

                //Make them all go away for a nanosec.
                TabPages.Clear();

                //Add them all back in.
                TabPages.AddRange((TabPage[])pages.ToArray(typeof(TabPage)));
                
                //Make sure the drag tab is selected.
                SelectedTab = drag_tab;
            }
        }
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

This class is fairly straight-forward, and should be fully inheritable and delegatable. To use it, you may build it into it's own assembly, or add it to a control library that you may already have. You can add it as a project to an existing solution, or simply add the .cs file to an existing project. Note that if you simply add the .cs file to a project, you also need to add the DraggableTabControl.bmp file to the project or get rid of the

[ToolboxBitmap]
attribute. If you make it into its own assembly or compile it with an existing control library, you may add it to the Toolbox by right-clicking it and selecting "Customize Toolbox..." and browsing to it under the ".NET Components" tab.

Other than that, the commented code pretty much speaks for itself. I have included the DraggableTabControl class source code in full below.

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace DraggableTabControl
{
    [ToolboxBitmap(typeof(DraggableTabControl))]
    /// 
    /// Summary description for DraggableTabPage.
    /// 
    public class DraggableTabControl : System.Windows.Forms.TabControl
    {
        ///  
        /// Required designer variable.
        /// 
        private System.ComponentModel.Container components = null;

        public DraggableTabControl()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            // TODO: Add any initialization after the InitForm call

        }

        ///  
        /// Clean up any resources being used.
        /// 
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

    #region Component Designer generated code
        ///  
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// 
        private void InitializeComponent()
        {
        }
    #endregion

        protected override void OnDragOver(System.Windows.Forms.DragEventArgs e)
        {
            base.OnDragOver(e);

            Point pt = new Point(e.X, e.Y);
            //We need client coordinates.
            pt = PointToClient(pt);

            //Get the tab we are hovering over.
            TabPage hover_tab = GetTabPageByTab(pt);

            //Make sure we are on a tab.
            if(hover_tab != null)
            {
                //Make sure there is a TabPage being dragged.
                if(e.Data.GetDataPresent(typeof(TabPage)))
                { 
                    e.Effect =   DragDropEffects.Move; 
                    TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));
                    int item_drag_index = FindIndex(drag_tab);
                    int drop_location_index= FindIndex(hover_tab); 

                    //Don't do anything if we are hovering over ourself.
                    if(item_drag_index ! = drop_location_index)  {

                        ArrayList pages = new ArrayList(); 
                        //Put all tab pages into an array.
                        for(int i = 0;  i <  TabPages.Count; i++)
                        {
                            //Except the one we are dragging.
                            if(i != item_drag_index)
                                pages.Add(TabPages[i]);
                        }

                        //Now put the one we are dragging it at the proper location.
                        pages.Insert(drop_location_index, drag_tab);

                        //Make them all go away for a nanosec.
                        TabPages.Clear();

                        //Add them all back in.
                        TabPages.AddRange((TabPage[])pages.ToArray(typeof(TabPage)));

                        //Make sure the drag tab is selected.
                        SelectedTab = drag_tab;
                    }
                }
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            Point pt = new Point(e.X, e.Y);
            TabPage tp = GetTabPageByTab(pt);

            if(tp != null)
            {
                DoDragDrop(tp, DragDropEffects.All);
            }
        }

        /// 
        /// Finds the TabPage whose tab is contains the given point.
        /// 
        /// The point (given in client coordinates) to look for a TabPage.
        /// The TabPage whose tab is at the given point (null if there isn't one).
        private TabPage GetTabPageByTab(Point pt)
        {
            TabPage tp =

                null;
            for(int i = 

                0;  i <  TabPages.Count; i++)
            {
                if(GetTabRect(i).Contains(pt))
                {
                    tp = TabPages[i];
                    break;
                }
            }

            return tp;
        }

        /// 
        /// Loops over all the TabPages to find the index of the given TabPage.
        /// 
        /// The TabPage we want the index for.
        /// The index of the given TabPage(-1 if it isn't found.)
        private int FindIndex(TabPage page)
        {
            for(int i = 0; i < TabPages.Count; i++)
            {
                if(TabPages[i] == page)
                    return i;
            }

            return -1;
        }
    }
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 3 Pin
Samuel W. Westing23-Mar-11 15:13
Samuel W. Westing23-Mar-11 15:13 
GeneralEvent when reordering is ready Pin
Carsten Giesen27-Nov-09 5:30
Carsten Giesen27-Nov-09 5:30 
GeneralGet rid of flicker on different sized tabs Pin
OffRoadRunner27-Aug-09 3:14
OffRoadRunner27-Aug-09 3:14 
GeneralRe: Get rid of flicker on different sized tabs Pin
Chris Knorr16-May-11 12:04
Chris Knorr16-May-11 12:04 
GeneralRe: Get rid of flicker on different sized tabs Pin
mjova17-Feb-13 22:30
mjova17-Feb-13 22:30 
GeneralLicensing inquiry Pin
2twotango9-Feb-09 10:22
2twotango9-Feb-09 10:22 
GeneralRemeber to Set "AllowDrop" Pin
sphudson11-Dec-08 4:54
sphudson11-Dec-08 4:54 
Generalnew small improvement Pin
felix at home5-Aug-06 8:32
felix at home5-Aug-06 8:32 
Generaldoes this work between tabcontrols or only within itself Pin
painlessprod28-Jul-06 13:43
painlessprod28-Jul-06 13:43 
GeneralRe: does this work between tabcontrols or only within itself [modified] Pin
Martin Howe19-May-08 3:05
Martin Howe19-May-08 3:05 
GeneralNice Work Pin
MANSATAN9-Mar-06 7:26
MANSATAN9-Mar-06 7:26 
GeneralExcellent Job - Here it is in VB Pin
Grant_Aust12-Feb-06 22:46
Grant_Aust12-Feb-06 22:46 
GeneralException in this code Pin
Eliot Shao15-Aug-05 19:00
Eliot Shao15-Aug-05 19:00 
GeneralRe: Exception in this code Pin
Paul Auger16-Aug-05 5:08
Paul Auger16-Aug-05 5:08 
GeneralRe: Exception in this code Pin
Eliot Shao16-Aug-05 15:05
Eliot Shao16-Aug-05 15:05 
GeneralFaster non-flickering code Pin
gabis28-Feb-05 22:24
gabis28-Feb-05 22:24 
GeneralRe: Faster non-flickering code Pin
titwan28-May-07 22:07
professionaltitwan28-May-07 22:07 
GeneralRe: Faster non-flickering code [modified] Pin
Rune Hunter22-Jun-07 18:29
Rune Hunter22-Jun-07 18:29 
GeneralCombined Pin
zx2c417-Dec-04 13:38
zx2c417-Dec-04 13:38 
QuestionHow to Copy TabPage Pin
DotNetNuke22-Oct-04 12:38
DotNetNuke22-Oct-04 12:38 
AnswerRe: How to Copy TabPage Pin
Paul Auger22-Oct-04 12:47
Paul Auger22-Oct-04 12:47 
GeneralRe: How to Copy TabPage Pin
ffonz7-Jul-05 11:38
ffonz7-Jul-05 11:38 
GeneralNon-flickering code Pin
jeffreykey12-Sep-03 20:59
jeffreykey12-Sep-03 20:59 
GeneralRe: Non-flickering code Pin
Paul Auger14-Sep-03 16:52
Paul Auger14-Sep-03 16:52 
GeneralNice but: Drag & Drop should start in MouseMove Pin
Yuval Naveh7-Sep-03 0:14
Yuval Naveh7-Sep-03 0:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.