Resizing canvas items

前端 未结 3 1740
Happy的楠姐
Happy的楠姐 2021-01-24 15:07

I\'m trying to create canvas which resizes its children when canvas itself is resized. So I create my own class which inherits from canvas and overrides method ArrangeOverride w

相关标签:
3条回答
  • 2021-01-24 15:23

    This may or may not exactly fit your requirements, but whenever I hear/read 'resize', I think of the UI control that was built just for that purpose; the Viewbox. Using this class, you have several options over how the content should be resized, but I think that you would want the default Uniform method.

    Just put whatever you need to be resized into a ViewBox and see how you like it... it should do the job nicely and simply for you:

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

    You don't actually need to add the Stretch="Uniform" here because that is the default... it's just here for demonstration purposes. To find out more, please see the Viewbox Class page on MSDN.

    0 讨论(0)
  • 2021-01-24 15:26

    Your custom panel should derive from Panel instead of Canvas and override the MeasureOverride and ArrangeOverride methods. Moreover it should define its own attached properties for child element layout, like the four properties RelativeX, RelativeY, RelativeWidth and RelativeHeight shown below.

    It would be used in XAML like this:

    <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>
    

    Here's the implementation:

    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;
        }
    }
    

    If you don't need the four layout properties to be independently bindable or settable by style setters etc. you could perhaps replace them by a single attached property of type Rect:

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

    with this much shorter implementation:

    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;
        }
    }
    
    0 讨论(0)
  • 2021-01-24 15:40

    Ok, so I tried messing around with this and I found some hacky way to make it work. I don't really understand why it works this way (yet), but this works for me:

    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;
        }
    }
    

    So the idea is to put ArrangeOverride() after the foreach loop in all situations except on first call.

    The first call must be before foreach or for some reason I get this:

    Invalid

    0 讨论(0)
提交回复
热议问题