是否有可能使WinForms Tab Control能够像IE或Firefox那样进行选项卡重新排序?

是否可以在运行时重新排序WinForms TabControl中的选项卡,如IE或Firefox?

像这样的链接并没有给我太多希望。

当然,这是可能的! 您最有可能尝试使解决方案过于复杂。 基本上,您所要做的就是将标准TabControl子类化,并为鼠标事件处理程序添加一些逻辑。 您只需要检查用户当前拖动的表单,并在TabPages集合中重新排序。

网上有几个完整的解决方案:

  • 在TabControl中重新排序TabPages
  • 拖放选项卡控件
  • 在运行时重新定位TabItems

我发现@Cody Gray最初发布的解决方案主要是我想要的,但我没有看到它需要如此复杂。

这是我的简化,通过派生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; } } 

拖放API实际上是用于在单独的应用程序之间拖动内容,或者至少是单独的控件。 在这种情况下使用它们是过度的。

如果你支持我的话,请确保你也支持Cody的答案,因为它是基于他的。

通过拖放重新排序TabPages由Ludwig B.
灵感来自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 ((eX - tcLocation.X) > ((hoverTabRect.X + hoverTabRect.Width) - dragTabRect.Width)) this.swapTabPages(tc, dragTab, hoverTab); } else if (dragTab_index > hoverTab_index) { if ((eX - 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(); } 

我延长了雅各布斯坦利的答案。 这样,交换不会经常发生。 这对于不同大小的选项卡特别有用,在这种情况下,先前的解决方案在拖动时会经常交换。

用户体验的不同之处在于您必须进一步拖动以实际移动选项卡。 但这类似于浏览器中的选项卡重新排序。

我还在拖动时添加了手形光标并启用了双缓冲。

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