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

笑着哭i 提交于 2020-01-02 01:54:05

问题


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.


回答1:


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



回答2:


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.




回答3:


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();
        }



回答4:


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;
        }
    }
}


来源:https://stackoverflow.com/questions/4352781/is-it-possible-to-make-the-winforms-tab-control-be-able-to-do-tab-reordering-lik

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!