14

Is it possible to rearrange tab items in tab control in run time? For example I have 3 tab items which are about cars and 4 tabs about house. I want to be able to rearrange them using drag and drop. Is it possible or it is something fantastic?

I have Tab Control here is XAML.

<TabControl x:Name="tc" Visibility="Collapsed" GotFocus="Focus" AllowDrop="True" >
            </TabControl>

Tab items will be added in runtime. Thanks for helping me!

Firdavs Kurbonov
  • 1,252
  • 4
  • 16
  • 42

2 Answers2

28

found a solution in the MSDN forum.

Here is the link:

DragDrop TabItem

Here is the solution:

C# solution

WPF code:

<TabControl>
    <TabControl.Resources>
        <Style TargetType="TabItem">
            <Setter Property="AllowDrop" Value="True"/>
                <EventSetter Event="PreviewMouseMove" Handler="TabItem_PreviewMouseMove"/>
                <EventSetter Event="Drop" Handler="TabItem_Drop"/>
        </Style>
    </TabControl.Resources>

    <TabItem Header="Tabitem 1"/>
    <TabItem Header="Tabitem 2"/>
    <TabItem Header="Tabitem 3"/>
    <TabItem Header="Tabitem 4"/>
    <TabItem Header="Tabitem 5"/>
</TabControl>

C# code behind:

private void TabItem_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (!(e.Source is TabItem tabItem))
    {
        return;
    }

    if (Mouse.PrimaryDevice.LeftButton == MouseButtonState.Pressed)
    {
        DragDrop.DoDragDrop(tabItem, tabItem, DragDropEffects.All);
    }
}

private void TabItem_Drop(object sender, DragEventArgs e)
{
    if (e.Source is TabItem tabItemTarget &&
        e.Data.GetData(typeof(TabItem)) is TabItem tabItemSource &&
        !tabItemTarget.Equals(tabItemSource) &&
        tabItemTarget.Parent is TabControl tabControl)
    {
        int targetIndex = tabControl.Items.IndexOf(tabItemTarget);

        tabControl.Items.Remove(tabItemSource);
        tabControl.Items.Insert(targetIndex, tabItemSource);
        tabItemSource.IsSelected = true;
    }
}
Dan
  • 7,286
  • 6
  • 49
  • 114
csteinmueller
  • 2,427
  • 1
  • 21
  • 32
  • Can you please give me code in c#, it will be appreciated. Thank you. – Firdavs Kurbonov May 25 '12 at 04:32
  • This code is working fine when Tab Item is empty, but my tab item has User Control in which there are many UI elements. Even I can put cursor to TextBox it is giving me error. Mouse click is working in each UI elements which is not what i want. i hope I could explain you. – Firdavs Kurbonov May 25 '12 at 10:39
  • The error is my mistake. Simply add if (tabItem == null) return; in the TabItem_PreviewMouseMove handler. But what do you mean with "Mouse click is working in each UI elements which is not what i want"?? – csteinmueller May 25 '12 at 10:47
  • It means when I click to my textbox, it is giving me that error. I can't put focus to my text box by mouse click in it. It is giving error when I click to any UI element. – Firdavs Kurbonov May 25 '12 at 10:57
  • But close button in tab item stopped working, I have close button in each tab item. Do you have idea how to fix it? – Firdavs Kurbonov May 25 '12 at 11:28
  • 1
    I am getting tabControl is null when i do var tabControl = tabItemTarget.Parent as TabControl; I have added the items using binding in MVVM pattern. How should i arrive at solution in my case. – Nomesh Gajare Aug 27 '13 at 10:19
  • I am having the same error was Nomesh... when I try to drop the element, I get the error at `if (!tabItemTarget.Equals(tabItemSource))` "Object reference not set to an instance of an object." – Jason Axelrod Oct 03 '14 at 03:35
  • In this case you need to find the Parent on the VisualTree. tabItemTarget.FindVisualParent() – Ketobomb Apr 08 '15 at 10:06
  • 3
    Great solution! Additional advice: Uncomment the last two lines if you want to reorder and not switch tabs. Also, if you implemented a close button, check if `!(e.OriginalSource is Button)` – bytecode77 Sep 28 '16 at 06:31
  • If the TabControl is bound to a collection, this solution may give errors that the collection cannot be updated while in use. To go around that, in the PreviewMouseMove use an object from the list as the second parameter (such as the selected item), and in the Drop modify the collection rather than the TabItems. – Daap Nov 30 '20 at 06:33
0

When I tried to implement this solution, the drop event was firing twice (moving the tabs but them immediately moving them back). I had to add an integer to keep track of the last tab target index. My solution is in VB.NET

'additional variable
Dim lastTabTargetIndex As Integer = Nothing

Private Sub tc1_PreviewMouseMove(sender As Object, e As MouseEventArgs) Handles tc1.PreviewMouseMove

    Dim Tabi = TryCast(e.Source, TabItem)

    If Tabi Is Nothing Then
        Exit Sub
    Else
        If Mouse.PrimaryDevice.LeftButton = MouseButtonState.Pressed Then
            DragDrop.DoDragDrop(Tabi, Tabi, DragDropEffects.All)
        End If
    End If
End Sub

Private Sub tc1_Drop(sender As Object, e As DragEventArgs) Handles tc1.Drop

    Dim tabItemTarget = TryCast(e.Source, TabItem)
    Dim tabItemSource = TryCast(e.Data.GetData(GetType(TabItem)), TabItem)

    If Not tabItemTarget.Equals(tabItemSource) Then
        Dim tabControl = TryCast(tabItemTarget.Parent, TabControl)
        Dim sourceIndex As Integer = tabControl.Items.IndexOf(tabItemSource)
        Dim targetIndex As Integer = tabControl.Items.IndexOf(tabItemTarget)

        'had to use this extra statement
        If sourceIndex <> lastTabTargetIndex Then
            'assign lastTabTargetIndex here
            lastTabTargetIndex = targetIndex
            tabControl.Items.Remove(tabItemSource)
            tabControl.Items.Insert(targetIndex, tabItemSource)
            tabControl.Items.Remove(tabItemTarget)
            tabControl.Items.Insert(sourceIndex, tabItemTarget)
        End If

    End If
End Sub
Thomas Bailey
  • 191
  • 2
  • 6