开发者

Is it possible to make the WinForms Tab Control be able to do tab reordering like IE or Firefox?

开发者 https://www.devze.com 2023-01-28 02:57 出处:网络
Is it possible to reorder the tabs in the WinForms开发者_运维问答 TabControl at run-time like IE or Firefox?

Is it possible to reorder the tabs in the WinForms开发者_运维问答 TabControl at run-time like IE or Firefox?

Links like this don't give me much hope.


Sure, it's possible! You're most likely trying to overcomplicate the solution. Essentially, all you have to do is subclass the standard TabControl and add some logic to the mouse event handlers. You'll just need to check which form the user is currently dragging and reorder it in the TabPages collection.

There are a couple of complete solutions available online:

  • Reordering TabPages inside TabControl
  • Drag and Drop Tab Control
  • Reposition TabItems at runtime


I found the solution originally posted by @Cody Gray to be mostly what I wanted, but I didn't see the need for it to be so complicated.

This is my simplification, implemented by deriving from TabControl:

public class DraggableTabControl : TabControl
{
    private TabPage m_DraggedTab;

    public DraggableTabControl()
    {
        MouseDown += OnMouseDown;
        MouseMove += OnMouseMove;
    }

    private void OnMouseDown(object sender, MouseEventArgs e)
    {
        m_DraggedTab = TabAt(e.Location);
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button != MouseButtons.Left || m_DraggedTab == null)
        {
            return;
        }

        TabPage tab = TabAt(e.Location);

        if (tab == null || tab == m_DraggedTab)
        {
            return;
        }

        Swap(m_DraggedTab, tab);
        SelectedTab = m_DraggedTab;
    }

    private TabPage TabAt(Point position)
    {
        int count = TabCount;

        for (int i = 0; i < count; i++)
        {
            if (GetTabRect(i).Contains(position))
            {
                return TabPages[i];
            }
        }

        return null;
    }

    private void Swap(TabPage a, TabPage b)
    {
        int i = TabPages.IndexOf(a);
        int j = TabPages.IndexOf(b);
        TabPages[i] = b;
        TabPages[j] = a;
    }
}

The drag and drop APIs are really intended for dragging stuff between separate applications, or at the very least, separate controls. Using them in this case is overkill.

Make sure you upvote Cody's answer too if you upvote mine, as it is based on his.


reordering TabPages with drag and drop - by Ludwig B.
inspired by http://dotnetrix.co.uk/tabcontrol.htm#tip7

        private void tc_MouseDown(object sender, MouseEventArgs e)
        {
            // store clicked tab
            TabControl tc = (TabControl)sender;
            int hover_index = this.getHoverTabIndex(tc);
            if (hover_index >= 0) { tc.Tag = tc.TabPages[hover_index]; }
        }
        private void tc_MouseUp(object sender, MouseEventArgs e)
        {
            // clear stored tab
            TabControl tc = (TabControl)sender;
            tc.Tag = null;
        }
        private void tc_MouseMove(object sender, MouseEventArgs e)
        {           
            // mouse button down? tab was clicked?
            TabControl tc = (TabControl)sender;
            if ((e.Button != MouseButtons.Left) || (tc.Tag == null)) return;
            TabPage clickedTab = (TabPage)tc.Tag;
            int clicked_index = tc.TabPages.IndexOf(clickedTab);

            // start drag n drop
            tc.DoDragDrop(clickedTab, DragDropEffects.All);
        }
        private void tc_DragOver(object sender, DragEventArgs e)
        {
            TabControl tc = (TabControl)sender;

            // a tab is draged?
            if (e.Data.GetData(typeof(TabPage)) == null) return;
            TabPage dragTab = (TabPage)e.Data.GetData(typeof(TabPage));
            int dragTab_index = tc.TabPages.IndexOf(dragTab);

            // hover over a tab?
            int hoverTab_index = this.getHoverTabIndex(tc);
            if (hoverTab_index < 0) { e.Effect = DragDropEffects.None; return; }
            TabPage hoverTab = tc.TabPages[hoverTab_index];
            e.Effect = DragDropEffects.Move;

            // start of drag?
            if (dragTab == hoverTab) return;

            // swap dragTab & hoverTab - avoids toggeling
            Rectangle dragTabRect = tc.GetTabRect(dragTab_index);
            Rectangle hoverTabRect = tc.GetTabRect(hoverTab_index);

            if (dragTabRect.Width < hoverTabRect.Width)
            {
                Point tcLocation = tc.PointToScreen(tc.Location);

                if (dragTab_index < hoverTab_index)
                {
                    if ((e.X - tcLocation.X) > ((hoverTabRect.X + hoverTabRect.Width) - dragTabRect.Width))
                        this.swapTabPages(tc, dragTab, hoverTab);
                }
                else if (dragTab_index > hoverTab_index)
                {
                    if ((e.X - tcLocation.X) < (hoverTabRect.X + dragTabRect.Width))
                        this.swapTabPages(tc, dragTab, hoverTab);
                }
            }
            else this.swapTabPages(tc, dragTab, hoverTab);

            // select new pos of dragTab
            tc.SelectedIndex = tc.TabPages.IndexOf(dragTab);
        }

        private int getHoverTabIndex(TabControl tc)
        {
            for (int i = 0; i < tc.TabPages.Count; i++)
            {
                if (tc.GetTabRect(i).Contains(tc.PointToClient(Cursor.Position)))
                    return i;
            }

            return -1;
        }

        private void swapTabPages(TabControl tc, TabPage src, TabPage dst)
        {
            int index_src = tc.TabPages.IndexOf(src);
            int index_dst = tc.TabPages.IndexOf(dst);
            tc.TabPages[index_dst] = src;
            tc.TabPages[index_src] = dst;
            tc.Refresh();
        }


I extended the answer of Jacob Stanley a bit. This way the swapping won't occur too often. This is especially helpful for tabs of different sizes in which case the previous solution would swap very often while dragging.

The difference in user experience is that you have to drag a bit further to actually move the tab. But this is similar to tab reordering in browsers.

Also I added a hand cursor while dragging and enabled double-buffering.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace Controls
{
    public class DraggableTabControl : TabControl
    {
        private TabPage draggedTab;

        public DraggableTabControl()
        {
            this.MouseDown += OnMouseDown;
            this.MouseMove += OnMouseMove;
            this.Leave += new System.EventHandler(this.DraggableTabControl_Leave);

            this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
        }

        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            draggedTab = TabAt(e.Location);
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left || draggedTab == null)
            {
                this.Cursor = this.DefaultCursor;
                draggedTab = null;
                return;
            }

            int index = TabPages.IndexOf(draggedTab);          
            int nextIndex = index + 1;
            int prevIndex = index - 1;

            int minXForNext = int.MaxValue;
            int maxXForPrev = int.MinValue;

            var tabRect = GetTabRect(index);

            if (nextIndex < TabPages.Count)
            {
                var nextTabRect = GetTabRect(nextIndex);
                minXForNext = tabRect.Left + nextTabRect.Width;
            }

            if (prevIndex >= 0)
            {
                var prevTabRect = GetTabRect(prevIndex);
                maxXForPrev = prevTabRect.Left + tabRect.Width;
            }

            this.Cursor = Cursors.Hand;

            if (e.Location.X > maxXForPrev && e.Location.X < minXForNext)
            {
                return;
            }

            TabPage tab = TabAt(e.Location);

            if (tab == null || tab == draggedTab)
            {
                return;
            }

            Swap(draggedTab, tab);
            SelectedTab = draggedTab;
        }

        private TabPage TabAt(Point position)
        {
            int count = TabCount;

            for (int i = 0; i < count; i++)
            {
                if (GetTabRect(i).Contains(position))
                {
                    return TabPages[i];
                }
            }

            return null;
        }

        private void Swap(TabPage a, TabPage b)
        {
            int i = TabPages.IndexOf(a);
            int j = TabPages.IndexOf(b);

            TabPages[i] = b;
            TabPages[j] = a;
        }

        private void DraggableTabControl_Leave(object sender, EventArgs e)
        {
            this.Cursor = this.DefaultCursor;
            draggedTab = null;
        }
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消