WPF:调整大小 Canvas 以包含其子项

WPF: Sizing Canvas to contain its children

我有一个任务是绘制多个矩形并对其进行操作。所以我使用 ItemsControlItemsPanel 作为 Canvas 和包装 ScrollViewer:

<ScrollViewer Height="200" Grid.Row="1" >
    <ItemsControl Name="rectanglesList" Background="AliceBlue">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding Y}"/>
                <Setter Property="Canvas.Top" Value="{Binding X}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border BorderThickness="1" BorderBrush="Black">
                    <Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding Color}"/>

                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

现在,由于 Canvas 高度和宽度属性不包括它的子项,因此 Scrollviewer 将不起作用,除非我通过计算求和来手动更改 ItemsControl 高度和宽度子项的高度和宽度并将它们分配给 ItemsControl

现在的问题是,是否有任何 属性 会自动执行此操作?

您可以使用覆盖 MeasureOverride 方法的自定义 Canvas

public class MyCanvas : Canvas
{
    protected override Size MeasureOverride(Size constraint)
    {
        base.MeasureOverride(constraint);

        var size = new Size();

        foreach (var child in Children.OfType<FrameworkElement>())
        {
            var x = GetLeft(child) + child.Width;
            var y = GetTop(child) + child.Height;

            if (!double.IsNaN(x) && size.Width < x)
            {
                size.Width = x;
            }

            if (!double.IsNaN(y) && size.Height < y)
            {
                size.Height = y;
            }
        }

        return size;
    }
}

并且可以这样使用:

<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <local:MyCanvas/>
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

它要求项目容器元素,除了 Canvas.LeftCanvas.Top 之外,还需要在 ItemContainerStyle 中设置 WidthHeight:

<ItemsControl.ItemContainerStyle>
    <Style TargetType="ContentPresenter">
        <Setter Property="Canvas.Left" Value="{Binding X}"/>
        <Setter Property="Canvas.Top" Value="{Binding Y}"/>
        <Setter Property="Width" Value="{Binding Width}"/>
        <Setter Property="Height" Value="{Binding Height}"/>
    </Style>
</ItemsControl.ItemContainerStyle>

ItemTemplate 看起来像这样:

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Border BorderThickness="1" BorderBrush="Black">
            <Rectangle Fill="{Binding Color}"/>
        </Border>
    </DataTemplate>
</ItemsControl.ItemTemplate>

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Rectangle StrokeThickness="1" Stroke="Black" Fill="{Binding Color}"/>
    </DataTemplate>
</ItemsControl.ItemTemplate>

您可能还想将 SCrollViewer 放入 ItemsControl 的 ControlTemplate 中:

<ItemsControl.Template>
    <ControlTemplate TargetType="ItemsControl">
        <ScrollViewer HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto">
            <ItemsPresenter/>
        </ScrollViewer>
    </ControlTemplate>
</ItemsControl.Template>

@Clemens 的回答很棒。我只是想添加一些上下文,说明为什么有必要这样做,以防不清楚。

在 WPF 中,Canvas 面板在被要求测量自身时不考虑它的 children。 parent 控件负责确保 canvas 的大小适合它将包含的项目。

在@Clemens提供的子类中,考虑所有children的逻辑在MeasureOverride中提供。

请注意,这不会考虑任何非FrameworkElement children。如果添加大量 children,您可能会注意到布局期间的性能下降。