我目前在.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;
}
}
几次单击按钮后,我得到类似...