首页 > 解决方案 > 如何调整 TableLayoutPanel 以按高度和重量包装内容?(动态)

问题描述

我创建了一个将用作弹出窗口的表单。
我需要这个弹出窗口包含标题、ProgressBar 和百分比文本。

因此,为此,我选择TableLayoutPanel在我的 Form 中用作控件。

所有这些 UI 元素都是动态创建的。为了测试,我现在使用标签而不是真正的 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 中的一个单元格,并且父窗体本身将通过包含所有其他控件的 TableLayoutPanel 调整其大小。

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

在此处输入图像描述

父表单不包装内容,看起来它具有固定大小,并且 TableLayoutPanel 尝试填充表单 ClientArea。我已经设置AutoSize = true;了,但它没有明显的影响。

我究竟做错了什么?

编辑

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

在此处输入图像描述

我还添加了

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

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

编辑2

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

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

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

在此处输入图像描述

为什么会这样?

标签: c#.netwinformstablelayoutpanel

解决方案


场景

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

如何进行

操作的顺序将清楚地确定最终结果。

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

最终的 Layout 将在 Form 显示之前执行:无论如何,此时,所有涉及的 Controls 都已按照指示调整大小和定位。

额外

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

此方法将表格布局重新应用到 TableLayoutPanel 中的所有控件。

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

关于此事的一些说明可以在这里找到:
这段代码(不同的语言,但易于阅读)可以轻松测试以重现问题
Dynamic TableLayoutPanel Controls Keep Border Width

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

删除 TableLayoutPanel 中的 Row 会导致布局问题
在 FlowLayoutPanel 中将多行控件居中


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

推荐阅读