【问题标题】:TabPage title alignment being wrong after drag'n'dropping拖放后 TabPage 标题对齐错误
【发布时间】:2015-07-27 03:30:37
【问题描述】:

我有一个扩展System.Windows.Forms.TabControl 的类,并为其TabPages 实现了拖放机制,如下所示:

#region Overriden base methods

protected override void OnDragOver(DragEventArgs e)
{
    if (PointedTabPage == null) return;

    e.Effect = DragDropEffects.Move;

    var dragTab = e.Data.GetData(typeof (ManagedTabPage)) as ManagedTabPage;

    if (dragTab == null) return;

    int dropIndex = TabPages.IndexOf(PointedTabPage);
    int dragIndex = TabPages.IndexOf(dragTab);

    if (dragIndex == dropIndex) return;

    var modifiedTabPages = new List<ManagedTabPage>(from ManagedTabPage tabPage in TabPages
                                                    where TabPages.IndexOf(tabPage) != dragIndex
                                                    select TabPages[TabPages.IndexOf(tabPage)] as ManagedTabPage);

    modifiedTabPages.Insert(dropIndex, dragTab);

    for (byte i = 0; i < TabPages.Count; ++i)
    {
        var managedTabPage = TabPages[i] as ManagedTabPage;

        if (managedTabPage != null && managedTabPage.Uid == modifiedTabPages[i].Uid)
            continue;

        TabPages[i] = modifiedTabPages[i];
    }

    SelectedTab = dragTab;
}

protected override void OnMouseDown(MouseEventArgs e)
{
    try
    {
        switch (e.Button)
        {
            case MouseButtons.Left:
                DoDragDrop(PointedTabPage, DragDropEffects.Move);

                break;
            case MouseButtons.Middle:
                CloseTab(PointedTabPage);

                break;
        }
    }
    catch (InvalidOperationException)
    {
    }
    finally
    {
        TabPages.Insert(0, String.Empty);
        TabPages.RemoveAt(0);
    }
}

#endregion

请注意,在 OnMouseDown 方法的 finally 子句中,有 2 行可解决
问题:由于某种原因,在拖放后没有这些行任何TabPages 的标题对齐都是错误的:

如果没有这种臭的解决方法,我应该怎么做才能纠正这种行为?也许发送一些 Windows 消息可以解决问题? 非常感谢您的任何建议。

编辑 1. ManagedTabPage 的代码 100% 不重要(它只是扩展了 TabPage 的一些特定属性)。

PointedTabPage 也不重要,但就是这样:

return (from ManagedTabPage tabPage in TabPages
        let tabPageIndex = TabPages.IndexOf(tabPage)
        where GetTabRect(tabPageIndex).Contains(PointToClient(Cursor.Position))
        select TabPages[tabPageIndex]).Single() as ManagedTabPage;

我正在尝试实现标签的完全居中对齐。你看,截图上的标签没有水平居中

【问题讨论】:

  • 代码示例现在不完整:PointedTabPage 是什么? ManagedTabPage 代码可能很重要。您要达到哪种对齐方式?
  • @ASh 我已经编辑了,请检查一下
  • PointedTabPage is unimportant too 你这么说真的很奇怪,因为PointedTabPage 被积极使用; CAN"T REPRODUCE ISSUE with given code and TabPage in placeof ManagedTabPage
  • @ASh 代码并不重要,因为它对对齐没有影响。
  • @ASh 设置你的TabControl的这些属性:AllowDrop = true; SizeMode = TabSizeMode.Fixed; ItemSize = new Size(224, 20);

标签: c# winforms drag-and-drop tabcontrol tabpage


【解决方案1】:

我对发布的代码无能为力。让我们采取完全不同的策略,创建一个支持用鼠标拖动标签页的标签控件。向您的项目添加一个新类并粘贴此代码:

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

class TabControlEx : TabControl {
    private Point downPos;
    private Form draggingHost;
    private Rectangle draggingBounds;
    private Point draggingPos;

    public TabControlEx() {
        this.SetStyle(ControlStyles.UserMouse, true);
    }
}

变量的用法稍后会变得清晰。我们需要的第一件事是从 TabControl 获取鼠标事件,以便我们可以看到用户试图拖动选项卡。这需要打开 UserMouse 控件样式,默认情况下对于内置 Windows 控件并使用自己的鼠标处理的控件(如 TabControl)是关闭的。

使用 Build > Build 并将新控件从工具箱顶部拖到窗体上。一切看起来和行为都像常规的 TabControl,但请注意单击选项卡不再更改活动选项卡。打开 UserMouse 样式的副作用。让我们先解决这个问题,粘贴:

protected override void OnMouseDown(MouseEventArgs e) {
    for (int ix = 0; ix < this.TabCount; ++ix) {
        if (this.GetTabRect(ix).Contains(e.Location)) {
            this.SelectedIndex = ix;
            break;
        }
    }
    downPos = e.Location;
    base.OnMouseDown(e);
}

我们正在存储点击位置,稍后需要它来检测用户拖动标签。这需要 MouseMove 事件,我们需要在用户将鼠标移动到距离原始点击位置足够远时开始拖动:

protected override void OnMouseMove(MouseEventArgs e) {
    if (e.Button == MouseButtons.Left && this.TabCount > 1) {
        var delta = SystemInformation.DoubleClickSize;
        if (Math.Abs(e.X - downPos.X) >= delta.Width ||
            Math.Abs(e.Y - downPos.Y) >= delta.Height) {
            startDragging();
        }
    }
    base.OnMouseMove(e);
}

startDragging 方法需要创建一个可以用鼠标移动的顶级窗口,其中包含我们正在拖动的选项卡的复制品。我们会将它显示为一个拥有的窗口,因此它始终位于顶部,与选项卡控件的大小完全相同:

private void startDragging() {
    draggingBounds = this.RectangleToScreen(new Rectangle(Point.Empty, this.Size));
    draggingHost = createDraggingHost(draggingBounds);
    draggingPos = Cursor.Position;
    draggingHost.Show(this.FindForm());
}

createDraggingHost 需要完成繁重的工作并创建一个看起来就像选项卡的窗口。我们将使用鼠标移动的无边框表单。我们将使用 TransparencyKey 属性使其看起来类似于拖动的 TabPage,并在顶部突出一个选项卡。让它看起来一样,只需让它显示标签页的屏幕截图:

private Form createDraggingHost(Rectangle bounds) {
    var host = new Form() { FormBorderStyle = FormBorderStyle.None, ControlBox = false, AutoScaleMode = AutoScaleMode.None, Bounds = this.draggingBounds, StartPosition = FormStartPosition.Manual };
    host.BackColor = host.TransparencyKey = Color.Fuchsia;
    var tabRect = this.GetTabRect(this.SelectedIndex);
    var tabImage = new Bitmap(bounds.Width, bounds.Height);
    using (var gr = Graphics.FromImage(tabImage)) {
        gr.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
        gr.FillRectangle(Brushes.Fuchsia, new Rectangle(0, 0, tabRect.Left, tabRect.Height));
        gr.FillRectangle(Brushes.Fuchsia, new Rectangle(tabRect.Right, 0, bounds.Width - tabRect.Right, tabRect.Height));
    }
    host.Capture = true;
    host.MouseCaptureChanged += host_MouseCaptureChanged;
    host.MouseUp += host_MouseCaptureChanged;
    host.MouseMove += host_MouseMove;
    host.Paint += (s, pe) => pe.Graphics.DrawImage(tabImage, 0, 0);
    host.Disposed += delegate { tabImage.Dispose(); };
    return host;
}

注意 Capture 属性的使用,这就是我们检测用户释放鼠标按钮或以任何其他方式中断操作的方式。我们将使用 MouseMove 事件来移动窗口:

private void host_MouseMove(object sender, MouseEventArgs e) {
    draggingHost.Location = new Point(draggingBounds.Left + Cursor.Position.X - draggingPos.X, 
                                      draggingBounds.Top + Cursor.Position.Y - draggingPos.Y);

}

最后我们需要处理拖动的完成。我们将交换选项卡,将拖动的选项卡插入鼠标位置并销毁窗口:

private void host_MouseCaptureChanged(object sender, EventArgs e) {
    if (draggingHost.Capture) return;
    var pos = this.PointToClient(Cursor.Position);
    for (int ix = 0; ix < this.TabCount; ++ix) {
        if (this.GetTabRect(ix).Contains(pos)) {
            if (ix != this.SelectedIndex) {
                var page = this.SelectedTab;
                this.TabPages.RemoveAt(this.SelectedIndex);
                this.TabPages.Insert(ix, page);
                this.SelectedIndex = ix;
            }
            break;
        }
    }
    draggingHost.Dispose();
    draggingHost = null;
}

看起来不错。

【讨论】:

  • 感谢 Hans,但您的建议会导致 UI 设计修改。我的问题是关于具体问题,而不是关于制作一般的可拖动控件。但无论如何都要投票。
  • 它不需要,您现有的 TabControl 不需要任何 UI 更改。只需将其从 TabControl 重命名为 TabControlEx 并删除您无法运行的代码即可。
  • 我这样做了,发现如果我将标签从他们的“轴”中删除,它将不会被替换。此外,可拖动选项卡不会与其主机分离 (TabControl)。您的解决方案非常漂亮,类似于很酷的 AvalonDock,但我的问题是另一个问题。经过一些校准后,我更愿意在其他项目中使用它,并与您分享。
  • 另外,如果我只做水平鼠标移动,drag'n'drop 效果很好。
【解决方案2】:

由于你没有分享ManagedTabPage代码,我使用默认TabPage控制

在方法OnDragOver中进行了更改

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace Demo
{
    public class MyTabControl : TabControl
    {
        public MyTabControl()
        {
            SizeMode = TabSizeMode.Fixed; 
            ItemSize = new Size(224, 20);
        }
        #region Overriden base methods

        protected override void OnDragOver(DragEventArgs e)
        {
            if (DesignMode)
                return;
            if (PointedTabPage == null) return;

            e.Effect = DragDropEffects.Move;

            var dragTab = e.Data.GetData(typeof(TabPage)) as TabPage;

            if (dragTab == null) return;

            int dropIndex = TabPages.IndexOf(PointedTabPage);
            int dragIndex = TabPages.IndexOf(dragTab);

            if (dragIndex == dropIndex) return;

            // change position of tab
            TabPages.Remove(dragTab);
            TabPages.Insert(dropIndex, dragTab);

            SelectedTab = dragTab;

            base.OnDragOver(e);
        }
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (DesignMode)
                return;

            switch (e.Button)
            {
                case MouseButtons.Left:
                    DoDragDrop(PointedTabPage, DragDropEffects.Move);                    

                    break;
                case MouseButtons.Middle:
                    TabPages.Remove(PointedTabPage);
                    break;
            }
        }

        #endregion
        TabPage PointedTabPage
        {
            get
            {
                return TabPages.OfType<TabPage>()
                    .Where((p, tabPageIndex) => GetTabRect(tabPageIndex).Contains(PointToClient(Cursor.Position)))
                    .FirstOrDefault();
            }
        }
    }
}

【讨论】:

  • 嗯,这可能就是我要找的。当我进行拖放时,只会出现一些闪烁。感谢您的建议,赏金是您的。
猜你喜欢
  • 1970-01-01
  • 2012-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-13
  • 2010-12-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多