WPF:调整项目大小以使所有项目可见

时间:2015-12-08 11:24:16

标签: wpf xaml

我有以下代码:

<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设置为我的网格,我会看到所有这些,但是当我最大化窗口时,我会有很多可用空间。

如果我没有设置任何宽度,我就是这样的: enter image description here

如果我设置宽度并增加尺寸,我就是这样的: enter image description here

目标是拥有类似第二种情况的东西,但不必设置宽度,并且如果我增加窗口大小,则将其缩放。

EDIT2 我尝试使用UniformGrid,但有两个问题。有两个元素,它似乎绝对想要有4列和3行。即使3列4行会更好: enter image description here

此外,当窗口缩小时,图像被剪切: enter image description here

5 个答案:

答案 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}}绑定的原因,我们   无需计算。

<强> 输出:

1 2 3

全屏:

4

答案 2 :(得分:0)

你可以试试这个。

<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <UniformGrid />
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

答案 3 :(得分:0)

这是相当模糊的,但您是否尝试过使用Blend for Visual Studio?它不仅可以帮助调试,还可以为WPF应用程序设计UI。从长远来看,它可能是最好的,因为您不必维护任何自定义控件/绑定。

答案 4 :(得分:-1)

您需要将RowDefinition更改为更像这样的内容;

<RowDefinition Height="*"/>

您的一行设置为Auto,这将尝试仅填充所需的空间。另一个设置为*,这将自动拉伸以填充它可以的所有空间。

请注意,无需输入</RowDefinition>,您只需以/>结尾即可。此链接可能对您特别有用;

Difference Between * and auto