【问题标题】:Resizing canvas items调整画布项目的大小
【发布时间】:2014-01-16 07:40:41
【问题描述】:

我正在尝试创建画布,当画布本身调整大小时,它会调整其子项的大小。所以我创建了我自己的类,它继承自画布并覆盖方法 ArrangeOverride,我在其中为画布中定义的所有子项设置位置和大小。 一切看起来都很好,但是当我调整应用程序的窗口大小时,项目的大小没有调整到正确的大小或位置。

这是一个简化的示例,它尝试将其元素对齐到画布的右边框:

Xaml 代码:

<Border BorderBrush="Black" BorderThickness="1" Margin="10" SnapsToDevicePixels="True">
    <l:CustomPanel>
        <Rectangle Fill="Red" />
        <Button>a</Button>
    </l:CustomPanel>
</Border>

自定义面板:

public class CustomPanel : Canvas
{
    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        var ret = base.ArrangeOverride(arrangeSize);
        var top = 0;

        foreach(UIElement child in Children)
        {                               
            Canvas.SetLeft(child, arrangeSize.Width - 20.0);                
            child.SetValue(WidthProperty, arrangeSize.Width - Canvas.GetLeft(child));                
            Canvas.SetTop(child, top);
            child.SetValue(HeightProperty, 20.0);
            top += 30;
        }
        return ret;
    }
}

当我改变窗口的宽度时,有时画布看起来像这张图片:

然后,如果我改变窗口的高度,项目会移动到正确的位置

我做错了什么? 我尝试将 SnapsToDevicePixels 设置为 True,但它对我不起作用。 :-(

【问题讨论】:

  • 你能解释一下你到底想要达到什么目标吗? Canvas 用于其子项的绝对定位。它不应该调整它们的大小。因此,您不应从 Canvas 派生自定义面板,而应从 Panel 派生。我很确定有比这更清洁的解决方案。
  • 此外,Panel从不设置其子元素的 Width 或 Height(或任何其他)属性。设置宽度或高度甚至可能在父面板上创建另一个布局通道。相反,通过调用 UIElement.Arrange.排列子元素
  • 这样做的原因是我必须创建类似图形的东西。所以有很多项目,必须在特定(百分比)位置,并且“图表”必须支持调整大小。我认为我可以计算画布中项目的特定位置。我在调整窗口大小后重新计算这些项目。您有什么更好的想法,如何实现这种行为?

标签: wpf canvas


【解决方案1】:

您的自定义面板应派生自 Panel 而不是 Canvas,并覆盖 MeasureOverrideArrangeOverride 方法。此外,它还应该为子元素布局定义自己的附加属性,如下所示的四个属性RelativeXRelativeYRelativeWidthRelativeHeight

它将像这样在 XAML 中使用:

<local:RelativeLayoutPanel>
    <Rectangle Fill="Red"
               local:RelativeLayoutPanel.RelativeX="0.2"
               local:RelativeLayoutPanel.RelativeY="0.1"
               local:RelativeLayoutPanel.RelativeWidth="0.6"
               local:RelativeLayoutPanel.RelativeHeight="0.8"/>
</local:RelativeLayoutPanel>

这是实现:

public class RelativeLayoutPanel: Panel
{
    public static readonly DependencyProperty RelativeXProperty = DependencyProperty.RegisterAttached(
        "RelativeX", typeof(double), typeof(RelativeLayoutPanel),
        new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));

    public static readonly DependencyProperty RelativeYProperty = DependencyProperty.RegisterAttached(
        "RelativeY", typeof(double), typeof(RelativeLayoutPanel),
        new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));

    public static readonly DependencyProperty RelativeWidthProperty = DependencyProperty.RegisterAttached(
        "RelativeWidth", typeof(double), typeof(RelativeLayoutPanel),
        new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));

    public static readonly DependencyProperty RelativeHeightProperty = DependencyProperty.RegisterAttached(
        "RelativeHeight", typeof(double), typeof(RelativeLayoutPanel),
        new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));

    public static double GetRelativeX(UIElement element)
    {
        return (double)element.GetValue(RelativeXProperty);
    }

    public static void SetRelativeX(UIElement element, double value)
    {
        element.SetValue(RelativeXProperty, value);
    }

    public static double GetRelativeY(UIElement element)
    {
        return (double)element.GetValue(RelativeYProperty);
    }

    public static void SetRelativeY(UIElement element, double value)
    {
        element.SetValue(RelativeYProperty, value);
    }

    public static double GetRelativeWidth(UIElement element)
    {
        return (double)element.GetValue(RelativeWidthProperty);
    }

    public static void SetRelativeWidth(UIElement element, double value)
    {
        element.SetValue(RelativeWidthProperty, value);
    }

    public static double GetRelativeHeight(UIElement element)
    {
        return (double)element.GetValue(RelativeHeightProperty);
    }

    public static void SetRelativeHeight(UIElement element, double value)
    {
        element.SetValue(RelativeHeightProperty, value);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

        foreach (UIElement element in InternalChildren)
        {
            element.Measure(availableSize);
        }

        return new Size();
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach (UIElement element in InternalChildren)
        {
            element.Arrange(new Rect(
                GetRelativeX(element) * finalSize.Width,
                GetRelativeY(element) * finalSize.Height,
                GetRelativeWidth(element) * finalSize.Width,
                GetRelativeHeight(element) * finalSize.Height));
        }

        return finalSize;
    }
}

如果您不需要四个布局属性可以独立绑定或由样式设置器等设置。您可以将它们替换为Rect 类型的单个附加属性:

<local:RelativeLayoutPanel>
    <Rectangle Fill="Red" local:RelativeLayoutPanel.RelativeRect="0.2,0.1,0.6,0.8"/>
</local:RelativeLayoutPanel>

有了这个更短的实现:

public class RelativeLayoutPanel: Panel
{
    public static readonly DependencyProperty RelativeRectProperty = DependencyProperty.RegisterAttached(
        "RelativeRect", typeof(Rect), typeof(RelativeLayoutPanel),
        new FrameworkPropertyMetadata(new Rect(), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));

    public static Rect GetRelativeRect(UIElement element)
    {
        return (Rect)element.GetValue(RelativeRectProperty);
    }

    public static void SetRelativeRect(UIElement element, Rect value)
    {
        element.SetValue(RelativeRectProperty, value);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

        foreach (UIElement element in InternalChildren)
        {
            element.Measure(availableSize);
        }

        return new Size();
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach (UIElement element in InternalChildren)
        {
            var rect = GetRelativeRect(element);

            element.Arrange(new Rect(
                rect.X * finalSize.Width,
                rect.Y * finalSize.Height,
                rect.Width * finalSize.Width,
                rect.Height * finalSize.Height));
        }

        return finalSize;
    }
}

【讨论】:

  • 这看起来比画布解决方案好得多并且可以正常工作。非常感谢。
  • 即使使用带有 DataTemplate 的 ItemsControl 也能完美运行。然后必须为附加属性使用样式设置器,因为 InternalChildren 返回包装 ContentPresenters
  • @Tarnschaf 我猜你想说的是,如果你使用 Panel 作为 ItemsControl 的 ItemsPanel,并且 Panel 在其子元素上使用附加属性进行布局,你将不得不在 ItemsContainerStyle 而不是 ItemTemplate 中设置这些附加属性以将它们应用于项目容器(即此处的 ContentPresenter)。这适用于所有使用附加属性进行布局的面板,但与 InternalChildren 属性无关。有关 Canvas 的示例,请参阅 this answer
  • 正是我想说的,但你肯定说得更好。
【解决方案2】:

好的,所以我尝试解决这个问题,并找到了一些让它工作的黑客方法。我真的不明白为什么它(还)以这种方式工作,但这对我有用:

public class CustomPanel : Canvas
{
    private bool isFirstArrange = true;

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var ret = new Size();
        bool isFirstArrangeLocal = isFirstArrange;
        if (isFirstArrangeLocal)
        {
            ret = base.ArrangeOverride(arrangeSize);
            isFirstArrange = false;
        }

        var top = 0;
        foreach (UIElement child in Children)
        {
            Canvas.SetLeft(child, arrangeSize.Width - 20.0);
            child.SetValue(WidthProperty, arrangeSize.Width - Canvas.GetLeft(child));
            Canvas.SetTop(child, top);
            child.SetValue(HeightProperty, 20.0);

            top += 30;
        }

        if (!isFirstArrangeLocal)
        {
            ret = base.ArrangeOverride(arrangeSize);
        }

        return ret;
    }
}

所以我们的想法是在除第一次调用之外的所有情况下都将ArrangeOverride() 放在 foreach 循环之后。

第一次调用必须在 foreach 之前或出于某种原因我得到这个:

【讨论】:

  • 它适用于这个简单的例子,谢谢。我正在尝试在我的实际项目中做同样的事情,但不幸的是它还不起作用。 :-(
【解决方案3】:

这可能完全符合您的要求,也可能不完全符合您的要求,但每当我听到/读到“调整大小”时,我都会想到专为此目的而构建的 UI 控件; Viewbox。使用这个类,您可以选择如何调整内容大小,但我认为您需要默认的 Uniform 方法。

只需将您需要调整大小的任何内容放入 ViewBox 中,然后看看您喜欢它...应该为您完成这项工作并简单:

<ViewBox Stretch="Uniform">
    <Canvas ... />
</ViewBox>

您实际上不需要在此处添加Stretch="Uniform",因为这是默认设置...只是出于演示目的。要了解更多信息,请参阅 MSDN 上的Viewbox Class 页面。

【讨论】:

    猜你喜欢
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 2013-10-06
    • 2011-03-14
    • 2013-12-15
    • 2013-11-19
    • 2014-11-04
    • 1970-01-01
    相关资源
    最近更新 更多