如何调整 TableLayoutPanel 按高度和重量包裹内容? (动态地)

How to adjust TableLayoutPanel to wrap content by height and weight? (dynamically)

我创建了一个表单,我将用作 pop-up。
我需要这个 pop-up 包含标题、ProgressBar 和百分比文本。

因此,为此,我选择使用 TableLayoutPanel 作为表单中的控件。

所有这些 UI 元素都是动态创建的。为了进行测试,我现在使用的是 Label 而不是真正的 ProgressBar。

public partial class TestFormDeleteIt : Form
{
    public TestFormDeleteIt()
    {
        InitializeComponent();

        AutoSize = true;
        var flp = CreateTableLayoutPanel();
        Controls.Add(flp);
    }

    private Control CreateTableLayoutPanel()
    {
        // TableLayoutPanel Initialization
        var panel = new TableLayoutPanel
        {
            ColumnCount = 2,
            RowCount = 2,
            Dock = DockStyle.Fill
        };

        //Adjust column size in percentage
        panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 70F));
        panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30F));

        //Adjust row size in percentage
        panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
        panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));

        //Fill content
        panel.Controls.Add(
            new Label()
            {
                Text = "state",
                Dock = DockStyle.Fill
            }, 
            /*column*/ 0, /*row*/ 0);

        panel.Controls.Add(
            new Label()
            {
                Text = "ProgressBar",
                Dock = DockStyle.Fill
            },
            /*column*/ 0, /*row*/ 1);

        panel.Controls.Add(
            new Label()
            {
                Text = "100%",
                Dock = DockStyle.Fill
            }, 
            /*column*/ 1, /*row*/ 1);
        return panel;
    }
}

因此,如您所见,我创建了 TableLayoutPanel 除以行和列,并为它包含的每个视图设置了 Dock = DockStyle.Fill,因为我不想硬编码固定大小。

我期望的是每个 UI 元素填充 TableLayoutPanel 中的一个单元格,并且 parent 表单本身将通过包含所有其他控件的 TableLayoutPanel 调整其大小。

但实际上我得到了这个结果:

Parent 表单不换行内容,看起来它具有固定大小并且 TableLayoutPanel 试图填充表单 ClientArea。我设置了 AutoSize = true;,但没有明显的影响。

我做错了什么?

编辑

谢谢,@Jimi 现在看起来好多了

我也加了

MinimumSize = new System.Drawing.Size(600, Height)

到 TLP,但问题是 - 如您所见,每一行都有类似的最小高度。每行之间看起来像 space。为什么会这样?

EDIT2

我想注意,如果我改变这个

panel.Controls.Add(
    new Label()
    {
        Text = "state",
        Dock = DockStyle.Fill
    },
    /*column*/ 0, /*row*/ 0);

对此:

var label1 = new Label()
{
    Text = "state",
    Dock = DockStyle.Fill
};

panel.SetRow(label1, 0);
panel.SetColumn(label1, 0);

它不起作用...我做错了什么?

这是我的代码

public partial class TestFormDeleteIt : Form
{
    public TestFormDeleteIt()
    {
        InitializeComponent();

        AutoSize = true;
        AutoSizeMode = AutoSizeMode.GrowAndShrink;
        MinimumSize = new System.Drawing.Size(200, 0);

        var flp = CreateTableLayoutPanel();
        Controls.Add(flp);
    }

    private TableLayoutPanel CreateTableLayoutPanel()
    {
        // TableLayoutPanel Initialization
        var panel = new TableLayoutPanel
        {
            ColumnCount = 2,
            RowCount = 2,
            Dock = DockStyle.Fill
        };

        //Adjust column size in percentage
        panel.ColumnStyles.Clear();
        panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 90F));
        panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 10F));

        //Adjust row size in percentage
        panel.RowStyles.Clear();
        panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
        panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));

        //Fill content
        panel.Controls.Add(
            new Label()
            {
                Text = "state",
                Dock = DockStyle.Fill
            },
            /*column*/ 0, /*row*/ 0);

        panel.Controls.Add(
            CreateProgressBar(),
            /*column*/ 0, /*row*/ 1);

        panel.Controls.Add(
            new Label()
            {
                Text = "100%",
                Dock = DockStyle.Fill
            }, 
            /*column*/ 1, /*row*/ 1);

        panel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        panel.AutoSize = true;

        return panel;
    }

    private Control CreateProgressBar() => new ProgressBar()
    {
        Visible = true,
        Dock = DockStyle.Top
    };
}

问题是 - 即使我在我的表单中将静态宽度设置为 200 -> MinimumSize = new Size(200, 0),当我打开它时我发现它需要 1000.

为什么会这样?

场景

  • 一个简单的表格,没有应用特定的尺寸。没有儿童控制。
  • A TableLayoutPanel 是在 运行 时从头开始构建的。无具体尺寸
    • 创建了两行和两列,大小模式设置为百分比
    • 从头开始创建三个控件并将其添加到 TableLayoutPanel 的三个单元格中
      TableLayoutPanel 必须根据新内容调整自身大小,新内容没有特定大小(应用到 Windows 表单控件的默认大小除外),不会 挤压 不存在新的子控件
  • 窗体在将 TableLayoutPanel 添加到其控件集合后,必须自动调整自身大小以适应其(唯一的)子控件的大小,保持新设置的最小宽度,但没有最小或最大高度(因此它如果需要,可以垂直扩展,但不能水平扩展)
  • 最后,自动调整大小的 TableLayoutPanel 必须停靠在自动调整大小的 Form 容器内,所有部分需要配合以生成预期的布局

如何进行

操作的顺序将明确决定最终结果。

  1. 表单初始化:它的构造函数调用标准的 InitializeComponent() 方法。没有子控件,因此实际上无需布局。表单将简单地设置在设计器中定义的大小。
  2. 我们现在指示表单根据其内容自动调整大小,并且允许它增大和缩小其大小以完成此任务。表单现在实际上不会做任何事情,因为它此时无法执行其布局(除非明确指示,使用 PerformLayout() 方法)
  3. 我们设置一个最小尺寸,限制它的宽度,但不限制它的高度(设置MinimumSize = new Size(200, 0),我们定义了一个最小宽度,但是高度尺寸是自由的增长和收缩,这里 - 设置 0 作为值,意味着 没有限制 )。
  4. 现在使用默认大小创建了 TableLayoutPanel 及其子控件(Windows 表单控件在创建时具有默认大小)。标签将自动调整到其文本的大小:这是默认行为。
  5. 将所有子控件添加到 TLP 后,指示 TLP 自动调整自身大小,还指定它可以增长和收缩以执行布局。 6 TableLayoutPanel 添加到 Form 容器中。现在,当控件添加到控件集合时,内部方法指示容器控件SuspendLayout(),定位新控件并ResumeLayou() (不执行子控件的布局,因为这刚刚发生):此时,指示自动调整其内容的表单将计算其 PreferredSize,然后自动调整大小本身到新的内容。
  6. TableLayoutPanel 现在设置为停靠到父 Container。 Container 已经自动调整为 TableLayoutPanel 的大小,因此 TLP 只是将其锚点固定为 Form 的当前大小。

最后的Layout 将在Form 显示之前执行:无论如何,在这一点上,所有涉及的Controls 都已经按照说明调整了大小和位置。

额外

► TableLayoutPanel 的 SetColumn() and SetRow() 被显式调用。原因在两种方法的备注部分有部分解释:

This method reapplies the table layout to all controls in the TableLayoutPanel.

在当前上下文中,调用这些方法是一个。这不是严格需要的,因为我们只是将控件添加到 TLP,从不删除控件。
但是,当控件被添加到 TLP 或从 TLP 中删除时,同时 TLP 设置为其内容自动调整大小(这适用于当前上下文),TLP 实际上不会按预期执行布局:在大多数情况下,单元格会(是的,不总是......)当一个控件被移除(移除或处置,结果是一样的)时保持它们原来的尺寸。

有关此事的一些注释可在此处找到:
此代码(不同语言但易于阅读)可以轻松测试以重现 问题

► 清除ColumnStyles and RowStyles并添加新样式:
- 原始代码实际上添加了新样式而不删除现有的默认样式:在某些情况下这会产生布局问题。这里有一些关于此事的注释:



public partial class TestFormDeleteIt : Form
{
    protected internal ProgressBar pBar = null;
    protected internal Label lbl2 = null;

    public TestFormDeleteIt()
    {
        InitializeComponent();

        this.AutoSize = true;
        this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        this.MinimumSize = new Size(200, 0);

        var tlp = CreateTableLayoutPanel();

        tlp.AutoSizeMode = AutoSizeMode.GrowAndShrink;
        tlp.AutoSize = true;

        this.Controls.Add(tlp);
        tlp.Dock = DockStyle.Fill;
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        Task.Run(() => UpdateProgress());
    }

    private TableLayoutPanel CreateTableLayoutPanel()
    {
        var panel = new TableLayoutPanel { ColumnCount = 2, RowCount = 2,
            CellBorderStyle = TableLayoutPanelCellBorderStyle.Single
        };
        panel.ColumnStyles.Clear();
        panel.RowStyles.Clear();

        panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 70F));
        panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30F));

        panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
        panel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));

        var lbl1 = new Label() { Text = "state", Dock = DockStyle.Fill };
        panel.Controls.Add(lbl1);
        panel.SetColumn(lbl1, 0);
        panel.SetRow(lbl1, 0);

        pBar = new ProgressBar() { Dock = DockStyle.Top };
        panel.Controls.Add(pBar);
        panel.SetColumn(pBar, 0);
        panel.SetRow(pBar, 1);

        lbl2 = new Label() { Text = "0%", Dock = DockStyle.Fill };
        panel.Controls.Add(lbl2);
        panel.SetColumn(lbl2, 1);
        panel.SetRow(lbl2, 1);

        return panel;
    }

    private async Task UpdateProgress()
    {
        for (int i = 1; i <= 100; i++) {
            BeginInvoke(new Action(() => {
                pBar.Value = i;
                lbl2.Text = (i / 100.0).ToString("P");
            }));
            await Task.Delay(50);
        }
    }
}