在 FlowLayoutPanel 中居中放置多行控件

Center multiple rows of controls in a FlowLayoutPanel

我正在尝试制作一个面板来承载动态添加的控件。有两个注意事项:

我看到了一些在表单中居中动态控件的提议解决方案,但出于以下原因拒绝了这些解决方案:

我想知道我是否缺少一些可以帮助我处理 grows/shrink 事件而无需创建我自己的 TableLayoutPanel 变体的功能?

编辑:
以下是功能草案:

这是一个重现您描述的行为的示例。
它使用承载多个 FlowLayoutPanel 的 TableLayoutPanel。

一个重要的细节是子 FlowLayoutPanel 的锚定:它们需要锚定到 Top-Bottom:这会导致面板位于 TableLayoutPanel 行的中心.

请注意,在 Form 构造函数中,RowStyles 之一已被删除。这也很重要:TLP(这是个古怪的家伙),即使你只有一行(或一列,同样的事情),也会保留 2 RowStyles。第二种样式将应用于您添加的第一行;只针对第一个,而不是其他的:这会搞砸布局。

另一个异常,它没有提供删除行的方法,所以我做了一个。它具有功能但很简单,需要扩展,包括进一步验证。

查看有关当前功能的图形示例。如果您在实施其他方面需要帮助,请发表评论。


要构建此控件,请将以下控件添加到窗体(此处称为 FLPTest1):

  1. 添加一个面板,设置Dock.Bottom。右击 SendToBack(),
  2. 添加一个TableLayoutPanel(这里叫tlp1),设置:
    • AutoScroll = trueAutoSize = true
    • AutoSizeMode = GrowAndShrink, Dock.Fill
    • 保留 1 列,设置为 AutoSize,保留 1 行,设置为 AutoSize
  3. 添加一个FlowLayoutPanel(这里叫flp1),定位在TableLayoutPanel里面。其实没必要,只是为了这个示例代码
    • 将其锚点设置为 Top, Bottom <= 这是 !important,没有它布局将无法正常工作:它允许将 FLPTLP 行内,
    • AutoSize = true, AutoSizeMode = GrowAndShrink
  4. 添加一个按钮(名为 btnAddControl
  5. 添加第二个按钮(名为 btnRemoveControl
  6. 添加复选框(名为chkRandom
  7. 将代码粘贴到表单的代码文件中

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


public partial class TLPTest1 : Form
{
    public TLPTest1()
    {
        InitializeComponent();
        tlp1.RowStyles.RemoveAt(1);
    }

    private void TLPTest1_Load(object sender, EventArgs e)
    {
        PictureBox pBox = new PictureBox() {
            Anchor = AnchorStyles.None,
            BackColor = Color.Orange,
            MinimumSize = new Size(125, 125),
            Size = new Size(125, 125),
        };
        flp1.Controls.Add(pBox);
        tlp1.Controls.Add(flp1);
    }

    Random rnd = new Random();
    Size[] sizes = new Size[] { new Size(75, 75), new Size(100, 100), new Size(125, 125)};
    Color[] colors = new Color[] { Color.Red, Color.LightGreen, Color.YellowGreen, Color.SteelBlue };
    Control selectedObject = null;

    private void btnAddControl_Click(object sender, EventArgs e)
    {
        Size size = new Size(125, 125);
        if (chkRandom.Checked) size = sizes[rnd.Next(sizes.Length)];
        
        var pBox = new PictureBox() {
            Anchor = AnchorStyles.None,
            BackColor = colors[rnd.Next(colors.Length)],
            MinimumSize = size,
            Size = size
        };

        bool drawborder = false;
        // Just for testing - use standard delegates instead of Lambdas in real code
        pBox.MouseEnter += (s, evt) => { drawborder = true;  pBox.Invalidate(); };
        pBox.MouseLeave += (s, evt) => { drawborder = false; pBox.Invalidate(); };
        pBox.MouseDown += (s, evt) => { selectedObject = pBox;  pBox.Invalidate(); };
        pBox.Paint += (s, evt) => { if (drawborder) {
            ControlPaint.DrawBorder(evt.Graphics, pBox.ClientRectangle, 
                                    Color.White, ButtonBorderStyle.Solid);
            }
        };

        var ctl = tlp1.GetControlFromPosition(0, tlp1.RowCount - 1);
        int overallWith = ctl.Controls.OfType<Control>().Sum(c => c.Width + c.Margin.Left + c.Margin.Right);
        overallWith += (ctl.Margin.Right + ctl.Margin.Left);

        if ((overallWith + pBox.Size.Width + pBox.Margin.Left + pBox.Margin.Right) >= tlp1.Width) {
            var flp = new FlowLayoutPanel() {
                Anchor = AnchorStyles.Top | AnchorStyles.Bottom,
                AutoSize = true,
                AutoSizeMode = AutoSizeMode.GrowAndShrink,
            };

            flp.Controls.Add(pBox);

            tlp1.SuspendLayout();
            tlp1.RowCount += 1;
            tlp1.Controls.Add(flp, 0, tlp1.RowCount - 1);
            tlp1.ResumeLayout(true);
        }
        else {
            ctl.Controls.Add(pBox);
        }
    }

    private void btnRemoveControl_Click(object sender, EventArgs e)
    {
        if (selectedObject is null) return;
        Control parent = selectedObject.Parent;
        selectedObject.Dispose();

        if (parent?.Controls.Count == 0) {
            TLPRemoveRow(tlp1, parent);
            parent.Dispose();
        }
    }

    private void TLPRemoveRow(TableLayoutPanel tlp, Control control)
    {
        int ctlPosition = tlp.GetRow(control);
        if (ctlPosition < tlp.RowCount - 1) {
            for (int i = ctlPosition; i < tlp.RowCount - 1; i++) {
                tlp.SetRow(tlp.GetControlFromPosition(0, i + 1), i);
            }
        }
        tlp.RowCount -= 1;
    }
}