同步TableLayoutPanel

时间:2011-11-12 02:59:57

标签: c# .net winforms tablelayoutpanel

我有几个TableLayoutPanel,每个都在两列中显示一个名称/值信息类别 - 一个包含信息标签,另一个包含数据标签。

在每一个中,我将第一列设置为自动调整大小并右对齐所有标签,这样可以正常工作。但是,它分别对每个TableLayoutPanel(显然)起作用,看起来像这样:

TableLayoutPanel 1:

+--------+--------+
| Name 1 | Data 1 |
+--------+--------+
| Name 2 | Data 2 |
+--------+--------+
| Name 3 | Data 3 |
+--------+--------+

TableLayoutPanel 2:

+------------------+--------+
|      Long Name 1 | Data 1 |
+------------------+--------+
| Very Long Name 2 | Data 2 |
+------------------+--------+
|      Long Name 3 | Data 3 |
+------------------+--------+

我正在寻找一种在自动调整所有的第一列时考虑所有名称标签的方法,所以它看起来像这样:

TableLayoutPanel 1:

+------------------+--------+
|           Name 1 | Data 1 |
+------------------+--------+
|           Name 2 | Data 2 |
+------------------+--------+
|           Name 3 | Data 3 |
+------------------+--------+

TableLayoutPanel 2:

+------------------+--------+
|      Long Name 1 | Data 1 |
+------------------+--------+
| Very Long Name 2 | Data 2 |
+------------------+--------+
|      Long Name 3 | Data 3 |
+------------------+--------+

我无法将所有数据放入一个表中,因为每个表代表不同类别的信息,并且位于具有可折叠面板集合的自定义控件内(因此您可以单独显示或隐藏每个类别)。 / p>

我一直试图通过覆盖容器控件OnLayout()来实现这一点,将所有TableLayoutPanels的第一列设置为自动调整大小,获取所有宽度,找到最大值,然后将所有第一列设置为固定尺寸的最大宽度。这样可行,但每次布局时看起来都很糟糕,因为所有列都跳转到自动调整大小然后再回到固定大小。

我假设我必须为每个表挂钩ControlAdded和ControlRemoved,然后为每个子控件添加SizeChanged,以便知道任何子控件的大小何时更改,然后以某种方式手动设置列宽,但我不确定如何可靠地获得正确的宽度。

我尝试了第一种方法的变体 - 在第一列中的所有控件上使用GetPreferredSize(),尝试找到最大宽度,然后将所有第一列设置为固定大小,但它似乎返回宽度有点小。我应该加一些额外的间距吗?

有没有人知道要求TableLayoutPanel执行自动调整大小计算的任何方法,而不实际应用它们?或者,也许,躺在桌子上“假装”有一定宽度的控制,只是考虑到它?我无法添加实际控件,因为它会为它们分配更多单元格。我尝试用ILSpy查看源代码,但是,它并不漂亮。似乎大部分工作都是由TableLayout类完成的,当然这是内部的,我无法按照它的做法进行操作。

感谢任何想法...

3 个答案:

答案 0 :(得分:3)

您可以使用Graphics.Measurestring确定长度(以像素为单位)而不实际绘制它。有一些slight imperfections with it,因此您可以考虑添加或删除一些填充。经过一两次测试,你可以非常接近。这就像我所知道的那样正确,并且它不涉及文本在标签中。

此外,尝试找到一种方法让TableLayoutPanel计算尺寸而不用直观地显示它只是听起来像是在试图破解它做一些它不适合的事情。

答案 1 :(得分:0)

这种方法不会因尺寸大小而闪烁或导致跳跃:

public partial class Form1 : Form
{
    private readonly Timer _timer = new Timer();

    public Form1()
    {
        InitializeComponent();

        _timer.Interval = 500;
        _timer.Tick += (o, ea) => UpdateWithRandomSizes();
        _timer.Start();
    }

    private void UpdateWithRandomSizes()
    {
        var rand = new Random();
        label1.Text = new string('A', rand.Next(10));
        label2.Text = new string('B', rand.Next(10));
        label3.Text = new string('C', rand.Next(10));
        label4.Text = new string('D', rand.Next(10));

        tableLayoutPanel1.ColumnStyles[0].SizeType = SizeType.AutoSize;
        tableLayoutPanel2.ColumnStyles[0].SizeType = SizeType.AutoSize;
        var width1 = tableLayoutPanel1.GetColumnWidths()[0];
        var width2 = tableLayoutPanel2.GetColumnWidths()[0];

        var max = Math.Max(width1, width2);

        tableLayoutPanel1.ColumnStyles[0].Width = max;
        tableLayoutPanel1.ColumnStyles[0].SizeType = SizeType.Absolute;
        tableLayoutPanel2.ColumnStyles[0].Width = max;
        tableLayoutPanel2.ColumnStyles[0].SizeType = SizeType.Absolute;
    }
}

答案 2 :(得分:0)

事实证明,GetPreferredSize()返回的宽度是有用的,它只是“为时已晚”;我正在计算正确的大小并在TableLayoutPanels的OnLayout()方法调用的代码中返回它,并且在 next 布局之前设置列宽没有任何效果。

我有一个解决方案,它使用了一个实现IExtenderProvider的独立组件,可以用来将表连接在一起,但由于上面的问题,它总是落后于控件更改。甚至在所有子控件上挂起SizeChanged,TableLayoutPanel首先获取事件,然后开始布局。

所以我看不到任何其他方式,只能覆盖布局过程本身。我尝试创建一个执行必要计算的LayoutEngine,调整列的大小,然后将实际的布局工作委托给旧的布局引擎,但不幸的是Control.LayoutEngine是只读的,而TableLayoutPanel甚至不使用支持字段,它只是直接返回另一个对象,所以我甚至无法通过使用反射来分配私有支持字段。

最后,我不得不求助于控制子类,以覆盖OnLayout()。结果如下:

public class SynchronizedTableLayoutPanel : TableLayoutPanel
    {
    /// <summary>
    /// Specifies a key used to group <see cref="SynchronizedTableLayoutPanel"/>s together.
    /// </summary>
    public String SynchronizationKey
        {
        get { return _SynchronizationKey; }
        set
            {
            if (!String.IsNullOrEmpty(_SynchronizationKey))
                RemoveSyncTarget(this);

            _SynchronizationKey = value;

            if (!String.IsNullOrEmpty(value))
                AddSyncTarget(this);
            }
        } private String _SynchronizationKey;

    #region OnLayout(), Recalculate()

    protected override void OnLayout(LayoutEventArgs levent)
        {
        if (ColumnCount > 0 && !String.IsNullOrEmpty(SynchronizationKey))
            {
            Recalculate();
            ColumnStyles[0] = new ColumnStyle(SizeType.Absolute, GetMaxWidth(SynchronizationKey));
            }

        base.OnLayout(levent);
        }

    public void Recalculate()
        {
        var LargestWidth = Enumerable.Range(0, RowCount)
            .Select(i => GetControlFromPosition(0, i))
            .Where(c => c != null)
            .Select(c => (Int32?)((c.AutoSize ? c.GetPreferredSize(new Size(Width, 0)).Width : c.Width)+ c.Margin.Horizontal))
            .Max();

        SetMaxWidth(this, LargestWidth.GetValueOrDefault(0));
        }

    #endregion

    #region (Static) Data, cctor, AddSyncTarget(), RemoveSyncTarget(), SetMaxWidth(), GetMaxWidth()

    private static readonly Dictionary<SynchronizedTableLayoutPanel, Int32> Data;

    static SynchronizedTableLayoutPanel()
        {
        Data = new Dictionary<SynchronizedTableLayoutPanel, Int32>();
        }

    private static void AddSyncTarget(SynchronizedTableLayoutPanel table)
        {
        Data.Add(table, 0);
        }

    private static void RemoveSyncTarget(SynchronizedTableLayoutPanel table)
        {
        Data.Remove(table);
        }

    private static void SetMaxWidth(SynchronizedTableLayoutPanel table, Int32 width)
        {
        Data[table] = width;

        foreach (var pair in Data.ToArray())
            if (pair.Key.SynchronizationKey == table.SynchronizationKey && pair.Value != width)
                pair.Key.PerformLayout();
        }

    private static Int32 GetMaxWidth(String key)
        {
        var MaxWidth = Data
            .Where(p => p.Key.SynchronizationKey == key)
            .Max(p => (Int32?) p.Value);

        return MaxWidth.GetValueOrDefault(0);
        }

    #endregion
    }

此版本仅关注第一列,但可以调整以同步其他列或行。