3

我有几个 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 |
+------------------+--------+

我不能将所有数据放在一个表中,因为每个表代表不同类别的信息,并且位于带有一组可折叠面板的自定义控件中(因此您可以单独显示或隐藏每个类别)。

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

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

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

有谁知道要求 TableLayoutPanel 执行自动大小计算而不实际应用它们的任何方式?或者,也许,对桌子撒谎以“假装”有一定宽度的控制,只是为了考虑到它?我不能添加实际的控件,因为它会想要为它们分配更多的单元格。我尝试使用 ILSpy 查看源代码,但它并不漂亮。似乎大部分工作都是由 TableLayout 类完成的,这当然是内部的,我无法理解它在做什么。

感谢您的任何想法...

4

3 回答 3

3

您可以使用Graphics.Measurestring来确定以像素为单位的长度,而无需实际绘制它。它有一些轻微的缺陷,因此您可能会考虑添加或删除一些填充。经过一两次测试,您可以非常接近。据我所知,这是一种正确的方式,它不涉及标签中的文本。

此外,试图找到一种方法让 TableLayoutPanel 计算大小而不以视觉方式显示它,这听起来就像你试图破解它做一些它不适合做的事情。

于 2011-11-13T05:06:52.037 回答
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;
    }
}
于 2011-11-12T06:16:21.900 回答
0

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

我有一个解决方案,它使用实现 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
    }

这个版本只关心第一列,但它可以适应同步其他列或行。

于 2011-11-13T21:28:47.293 回答