WPF:调整画布大小以容纳其子级

时间:2019-03-11 11:41:15

标签: c# wpf canvas itemscontrol

我有一个任务来绘制多个矩形并对其进行操作。因此,我使用的是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的height和width属性不包含子元素,因此除非我通过计算子元素的高度和宽度之和来手动更改Scrollviewer的高度和宽度,否则ItemsControl将不起作用并将它们分配给ItemsControl

现在的问题是,是否缺少任何自动执行的属性?

2 个答案:

答案 0 :(得分:1)

您可以使用覆盖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之外,还要求item容器元素在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>

答案 1 :(得分:1)

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

在WPF中,Canvas面板在被要求进行自我测量时不会考虑其子级。父控件的责任是确保画布的大小适合将包含的项目。

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

请注意,这不会考虑任何非FrameworkElement的孩子。如果添加大量子代,可能会在布局过程中发现性能下降。