我有以下代码:
<ItemsControl ItemsSource="{Binding SubItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Image Source="{Binding Image}" ></Image>
<TextBlock Text="{Binding Name}" Grid.Row="1" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
目前,如果我运行此代码,则每个项目(网格)都会尝试获取可用的全部空间,而我在SubItems
个集合中的20多个项目中只能看到1-2个项目。
如果我将MaxWidth
设置为我的网格,我会看到所有这些,但是当我最大化窗口时,我会有很多可用空间。
目标是拥有类似第二种情况的东西,但不必设置宽度,并且如果我增加窗口大小,则将其缩放。
EDIT2 我尝试使用UniformGrid,但有两个问题。有两个元素,它似乎绝对想要有4列和3行。即使3列4行会更好:
答案 0 :(得分:4)
如果没有别的帮助,请考虑编写自己的面板。我现在没有时间寻找完整的解决方案,但请考虑一下。
首先,按照你想要的方式平铺矩形方块并不是一件容易的事。这被称为包装问题,解决方案通常很难找到(取决于具体问题)。我已经采用算法从这个问题中找到近似的图块大小:Max square size for unknown number inside rectangle。
如果面板的给定宽度和高度具有方形尺寸,则其余部分更容易:
public class AdjustableWrapPanel : Panel {
protected override Size MeasureOverride(Size availableSize) {
// get tile size
var tileSize = GetTileSize((int) availableSize.Width, (int) availableSize.Height, this.InternalChildren.Count);
foreach (UIElement child in this.InternalChildren) {
// measure each child with a square it should occupy
child.Measure(new Size(tileSize, tileSize));
}
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize) {
var tileSize = GetTileSize((int)finalSize.Width, (int)finalSize.Height, this.InternalChildren.Count);
int x = 0, y = 0;
foreach (UIElement child in this.InternalChildren)
{
// arrange in square
child.Arrange(new Rect(new Point(x,y), new Size(tileSize, tileSize)));
x += tileSize;
if (x + tileSize >= finalSize.Width) {
// if need to move on next row - do that
x = 0;
y += tileSize;
}
}
return finalSize;
}
int GetTileSize(int width, int height, int tileCount)
{
if (width*height < tileCount) {
return 0;
}
// come up with an initial guess
double aspect = (double)height / width;
double xf = Math.Sqrt(tileCount / aspect);
double yf = xf * aspect;
int x = (int)Math.Max(1.0, Math.Floor(xf));
int y = (int)Math.Max(1.0, Math.Floor(yf));
int x_size = (int)Math.Floor((double)width / x);
int y_size = (int)Math.Floor((double)height / y);
int tileSize = Math.Min(x_size, y_size);
// test our guess:
x = (int)Math.Floor((double)width / tileSize);
y = (int)Math.Floor((double)height / tileSize);
if (x * y < tileCount) // we guessed too high
{
if (((x + 1) * y < tileCount) && (x * (y + 1) < tileCount))
{
// case 2: the upper bound is correct
// compute the tileSize that will
// result in (x+1)*(y+1) tiles
x_size = (int)Math.Floor((double)width / (x + 1));
y_size = (int)Math.Floor((double)height / (y + 1));
tileSize = Math.Min(x_size, y_size);
}
else
{
// case 3: solve an equation to determine
// the final x and y dimensions
// and then compute the tileSize
// that results in those dimensions
int test_x = (int)Math.Ceiling((double)tileCount / y);
int test_y = (int)Math.Ceiling((double)tileCount / x);
x_size = (int)Math.Min(Math.Floor((double)width / test_x), Math.Floor((double)height / y));
y_size = (int)Math.Min(Math.Floor((double)width / x), Math.Floor((double)height / test_y));
tileSize = Math.Max(x_size, y_size);
}
}
return tileSize;
}
}
答案 1 :(得分:1)
像这样创建DataTemplate
:
<DataTemplate>
<Grid Height="{Binding RelativeSource={RelativeSource Self},Path=ActualWidth,Mode=OneWay}">
<Grid.Width>
<MultiBinding Converter="{StaticResource Converter}">
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="ActualWidth" Mode="OneWay" />
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="ActualHeight" Mode="OneWay" />
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="DataContext.SubItems.Count" />
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="ActualWidth" />
</MultiBinding>
</Grid.Width>
<Grid.RowDefinitions>
<强> 转换器: 强>
public class Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double TotalWidth = System.Convert.ToDouble(values[0]), TotalHeight = System.Convert.ToDouble(values[1]);
int TotalItems = System.Convert.ToInt32(values[2]);
var TotalArea = TotalWidth * TotalHeight;
var AreasOfAnItem = TotalArea / TotalItems;
var SideOfitem = Math.Sqrt(AreasOfAnItem);
var ItemsInCurrentWidth = Math.Floor(TotalWidth / SideOfitem);
var ItemsInCurrentHeight = Math.Floor(TotalHeight / SideOfitem);
while (ItemsInCurrentWidth * ItemsInCurrentHeight < TotalItems)
{
SideOfitem -= 1;//Keep decreasing the side of item unless every item is fit in current shape of window
ItemsInCurrentWidth = Math.Floor(TotalWidth / SideOfitem);
ItemsInCurrentHeight = Math.Floor(TotalHeight / SideOfitem);
}
return SideOfitem;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
逻辑解释: 这种方法非常简单。计算ItemsControl
的面积,并在所有项目中划分相等的面积。这也是视觉上可能的最佳场景。因此,如果列表中有20个项目,区域为2000单位(方形),则每个项目将获得100个单位的区域进行渲染。
现在棘手的部分是ItemsControl
的区域不能是方形 ,但项目总是呈方形。因此,如果我们想要显示所有项目而没有任何区域被溢出修剪,我们需要减少每个项目的面积,直到它适合当前的形状。 while
中的Converter
循环通过计算所有项目是否完全visible
来完成此操作。如果所有项目都不完全可见,则知道需要减小尺寸。
注意: 每个项目都是相同的
Height
&amp;Width
(方形区域)。这就是Height
的{{1}}与Grid
的{{1}}绑定的原因,我们 无需计算。
<强> 输出: 强>
全屏:
答案 2 :(得分:0)
你可以试试这个。
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
答案 3 :(得分:0)
这是相当模糊的,但您是否尝试过使用Blend for Visual Studio?它不仅可以帮助调试,还可以为WPF应用程序设计UI。从长远来看,它可能是最好的,因为您不必维护任何自定义控件/绑定。
答案 4 :(得分:-1)
您需要将RowDefinition
更改为更像这样的内容;
<RowDefinition Height="*"/>
您的一行设置为Auto
,这将尝试仅填充所需的空间。另一个设置为*
,这将自动拉伸以填充它可以的所有空间。
请注意,无需输入</RowDefinition>
,您只需以/>
结尾即可。此链接可能对您特别有用;