我目前在.NET应用程序中使用FlowLayoutPanel。但是,我想知道是否有其他选择可以使子代表现得像float:两者在HTML中都一样。因此,一个元素大小是其他元素的两倍,它旁边将有2行,而不是1行。



最佳地,它也将在双方上都起作用。



注意:这是Windows窗体,我只是使用Metro风格的控件。

最佳答案

这听起来很有趣,所以我想我去尝试一下。

首先,快速测试表格...

我创建了带有顶部停靠按钮的form1,并将容器停靠了填充容器,并添加了以下代码。请注意,我选择了三种平铺尺寸,以使其与5像素的余量完全吻合。

    int tileIndex = 0;

    const int tileSmall = 30;
    const int tileMed = 70;
    const int tileLarge = 110;

    private void button1_Click(object sender, EventArgs e)
    {
        Label l = new Label();
        l.AutoSize = false;
        l.BackColor = Color.Green;
        l.TextAlign = ContentAlignment.MiddleCenter;
        l.Margin = new Padding(5);
        l.Text = string.Format("Tile {0}", tileIndex++);

        switch (r.Next(3))
        {
            case 0: l.Width = tileSmall; break;
            case 1: l.Width = tileMed; break;
            case 2: l.Width = tileLarge; break;
        }

        switch (r.Next(3))
        {
            case 0: l.Height = tileSmall; break;
            case 1: l.Height = tileMed; break;
            case 2: l.Height = tileLarge; break;
        }

        flowLayoutPanel1.Controls.Add(l);
    }


现在是一个我们可以控制布局的自定义面板。...从FlowLayoutPanel中偷了一点

class CompactFlowPanel : Panel, IExtenderProvider
{
    public override System.Windows.Forms.Layout.LayoutEngine LayoutEngine
    {
        get
        {
            return CompactFlow.Instance;
        }
    }

    public bool CanExtend(object extendee)
    {
        Control control = extendee as Control;
        return control != null && control.Parent == this;
    }
}


现在,布局引擎以矩形块的形式跟踪可用空间,以便它可以尝试将下一块压缩到其中。

class CompactFlow : LayoutEngine
{
    private static CompactFlow instance;

    public static CompactFlow Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new CompactFlow();
            }

            return instance;
        }
    }

    public override bool Layout(object container, LayoutEventArgs layoutEventArgs)
    {
        var cont = container as Control;
        if (cont != null)
            return LayoutCore(cont);
        else
            return base.Layout(container, layoutEventArgs);
    }

    private bool LayoutCore(Control container)
    {
        List<Rectangle> emptyTiles = new List<Rectangle>();

        emptyTiles.Add(new Rectangle(Point.Empty, container.Size));

        for (int i = 0; i < container.Controls.Count; i++)
        {
            var control = container.Controls[i];

            int j = 0;
            var tile = GetNextEmptyTile(emptyTiles, j++);

            //We don't know the location of the control yet, so we are only interested in size for this comparison
            while (!IsBigger(tile.Size, control.Size))
            {
                tile = GetNextEmptyTile(emptyTiles, j++);
                if (tile == Rectangle.Empty)
                {
                    //?! Out of space...
                    break;
                }
            }

            PlaceControl(emptyTiles, tile, control);

        }

        return true;
    }

    private void PlaceControl(List<Rectangle> emptyTiles, Rectangle tile, Control control)
    {
        //Place the control and work out how much space we have used
        control.Left = tile.Left + control.Margin.Left;
        control.Top = tile.Top + control.Margin.Top;

        var usedArea = GetUsedSpace(control);
        //When we place place the control, split the empty space it went into used/empty space
        //Add the Empty space back the list of empty tiles
        SplitTile(emptyTiles, tile, usedArea);

        //At this point the empty tiles over lap and the new control may intersect with a previously empty tile, that tile also needs splitting.
        for (int i = emptyTiles.Count - 1; i >= 0; i--)
        {
            var tileCheck = emptyTiles[i];
            if (tileCheck.IntersectsWith(usedArea))
            {
                SplitTile(emptyTiles, tileCheck, usedArea);
            }
        }

        //Now we're left with a bit of a mess of emptyTiles, They might eventually get cleaned up but we'll run a "quick" check here to see if any tile
        //completely contains another, this will speed up future searches for empty tiles.
        //We could extend this further to merge to empty tiles next to each other with have the same height or width
        for (int i = emptyTiles.Count - 1; i >= 0; i--)
        {
            var tile1 = emptyTiles[i];
            for (int j = emptyTiles.Count - 1; j >= 0; j--)
            {
                if (i != j)
                {
                    var tile2 = emptyTiles[j];
                    if (tile1.Contains(tile2))
                    {
                        emptyTiles.Remove(tile2);

                        if (j < i)
                            i--;
                    }
                }
            }
        }

        //The final trick is to sort the empty spaces, by y first then by x, This essentially determines the flow direction
        emptyTiles.Sort(new Comparison<Rectangle>((r1, r2) =>
        {
            if (r1.Y == r2.Y)
            {
                return r1.X.CompareTo(r2.X);
            }
            return r1.Y.CompareTo(r2.Y);
        }));
    }

    private static Rectangle GetUsedSpace(Control control)
    {

        var usedSpace = control.Size;
        usedSpace.Width += control.Margin.Horizontal;
        usedSpace.Height += control.Margin.Vertical;

        var usedLocation = control.Location;
        usedLocation.X -= control.Margin.Left;
        usedLocation.Y -= control.Margin.Top;
        var usedArea = new Rectangle(usedLocation, usedSpace);
        return usedArea;
    }

    private static void SplitTile(List<Rectangle> emptyTiles, Rectangle tile, Rectangle usedArea)
    {
        var controlWidth = usedArea.Width;
        var controlHeight = usedArea.Height;
        //if the empty space is wider than the new space then make 2 new empty spaces either side of the used space,
        //the same height as the original empty space.
        if (tile.Width > controlWidth)
        {
            var newTile1 = new Rectangle(tile.Left, tile.Top, usedArea.Left - tile.Left, tile.Height);
            //If the used space was up against the edge than the new space wont be viable
            if (newTile1.Width > 0)
            {
                emptyTiles.Add(newTile1);
            }

            var newTile2 = new Rectangle(usedArea.Right, tile.Top, tile.Right - usedArea.Right, tile.Height);
            if (newTile2.Width > 0)
            {
                emptyTiles.Add(newTile2);
            }
        }

        if (tile.Height > controlHeight)
        {
            var newTile1 = new Rectangle(tile.Left, tile.Top, tile.Width, usedArea.Top - tile.Top);
            if (newTile1.Height > 0)
            {
                emptyTiles.Add(newTile1);
            }

            var newTile2 = new Rectangle(tile.Left, usedArea.Bottom, tile.Width, tile.Bottom - usedArea.Bottom);
            if (newTile2.Height > 0)
            {
                emptyTiles.Add(newTile2);
            }
        }

        //Remove the original as it now contains used space.
        emptyTiles.Remove(tile);
    }

    private bool IsBigger(Size size1, Size size2)
    {
        return (size1.Width >= size2.Width && size1.Height >= size2.Height);
    }

    private Rectangle GetNextEmptyTile(List<Rectangle> emptyTiles, int j)
    {
        if (j < emptyTiles.Count)
            return emptyTiles[j];
        else
            return Rectangle.Empty;
    }
}


几次单击按钮后,我得到类似...

10-05 19:23