Mi último invento: RelativeCanvas

No, chaval, no estoy hablando de que es el último invento que hago, sino el más reciente Sonrisa

Me ha dado por hurgar en las tripas de WPF  y de vez en cuando me salta una lagrimilla. ¡Qué frescura! Esto es complejo y elegante a partes iguales. Cualquiera que lea esto va a pensar que acabo de descubrirlo… ¡pero es que nunca dejas de sorprenderte!

Pues a lo que iba: hay en WPF flexibilidad para hacer casi de todo. Uno de los paneles que se podrían llegar a echar en falta es el que yo llamo RelativeCanvas.

Se trata de un Canvas que recalcula las posiciones de sus hijos en relación a una proporción. Me explico: si pongo un hijo en el centro y redimensiono el Canvas, el objeto queda en el centro haga lo que haga. El efecto que se obtiene es el mismo que al hacer zoom en una aplicación de mapas, que los iconos y etiquetas tienen el mismo tamaño, pero su separación entre los mismos es mayor.

Antes (tamaño pequeño)

image

Después (tamaño grande). Al estirar, todo ocupa si lugar relativo (al tamaño del Canvas)

image

“Y lo mejor de todo es que mi marido ha dejado de roncar”

Aquí tienes el código, para que te pongas feliz Risa

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace RelativePanelTest {
    public class RelativeCanvas : Canvas {
        public RelativeCanvas() {
            Loaded += OnLoaded;
            Unloaded += OnUnloaded;
        }

        private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) {
            SizeChanged -= OnSizeChanged;
        }

        void OnLoaded(object sender, RoutedEventArgs e) {
            foreach (UIElement child in Children) {
                UpdateHorizontalProportions(child);
                UpdateVerticalProportions(child);
            }

            SizeChanged += OnSizeChanged;
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs) {

            foreach (UIElement uiElement in Children) {

                if (sizeChangedEventArgs.WidthChanged) {
                    var relativeX = (double) uiElement.GetValue(HorizontalProportionProperty);

                    if (!double.IsInfinity(relativeX)) {
                        var newX = relativeX*sizeChangedEventArgs.NewSize.Width;
                        SetLeftOfRelativePoint(uiElement, newX);
                    }
                }

                if (sizeChangedEventArgs.HeightChanged) {
                    var relativeY = (double)uiElement.GetValue(VerticalProportionProperty);

                    if (!double.IsInfinity(relativeY)) {
                        var newY = relativeY * sizeChangedEventArgs.NewSize.Height;
                        SetTopOfRelativePoint(uiElement, newY);
                    }
                }
            }
        }

        protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) {

            if (visualAdded != null) {
                if (IsLoaded) {
                    UpdateHorizontalProportions(visualAdded);
                    UpdateVerticalProportions(visualAdded);
                }
                AttachToPositionChanged(visualAdded);
            }
            if (visualRemoved != null) {
                DettachToPositionChanged(visualAdded);
            }

            base.OnVisualChildrenChanged(visualAdded, visualRemoved);
        }

        private void AttachToPositionChanged(DependencyObject visualAdded) {
            var leftDescriptor = DependencyPropertyDescriptor.FromProperty(LeftProperty, typeof(Canvas));
            leftDescriptor.AddValueChanged(visualAdded, OnChildrenLeftChanged);

            var topDescriptor = DependencyPropertyDescriptor.FromProperty(TopProperty, typeof(Canvas));
            topDescriptor.AddValueChanged(visualAdded, OnChildrenTopChanged);
        }

        private void DettachToPositionChanged(DependencyObject visualAdded) {
            var leftDescriptor = DependencyPropertyDescriptor.FromProperty(LeftProperty, typeof(Canvas));
            leftDescriptor.RemoveValueChanged(visualAdded, OnChildrenLeftChanged);

            var topDescriptor = DependencyPropertyDescriptor.FromProperty(TopProperty, typeof(Canvas));
            topDescriptor.RemoveValueChanged(visualAdded, OnChildrenTopChanged);
        }

        private void UpdateHorizontalProportions(DependencyObject dependencyObject) {

            var currentX = GetLeftOfRelativePoint((UIElement)dependencyObject);

            var relativeX = currentX / ActualWidth;
            SetHorizontalProportion(dependencyObject, relativeX);
        }

        private void UpdateVerticalProportions(DependencyObject dependencyObject) {
            var currentY = GetTopOfRelativePoint((UIElement)dependencyObject);

            var relativeY = currentY / ActualHeight;

            if (!double.IsNaN(relativeY))
                SetVerticalProportion(dependencyObject, relativeY);
        }

        private void OnChildrenLeftChanged(object sender, EventArgs eventArgs) {
            var child = (DependencyObject)sender;
            UpdateHorizontalProportions(child);
        }

        private void OnChildrenTopChanged(object sender, EventArgs e) {
            var child = (DependencyObject)sender;
            UpdateVerticalProportions(child);
        }

        private static double GetLeftOfRelativePoint(UIElement uiElement) {

            var renderOrigin = (Point)uiElement.GetValue(RelativeOriginProperty);

            return GetLeft(uiElement) + renderOrigin.X * uiElement.RenderSize.Width;
        }

        private static double GetTopOfRelativePoint(UIElement uiElement) {

            var renderOrigin = (Point)uiElement.GetValue(RelativeOriginProperty);

            return GetTop(uiElement) + renderOrigin.Y * uiElement.RenderSize.Height;
        }

        private static void SetLeftOfRelativePoint(UIElement uiElement, double left) {

            var renderOrigin = (Point)uiElement.GetValue(RelativeOriginProperty);

            SetLeft(uiElement, left - renderOrigin.X * uiElement.RenderSize.Width);
        }

        private static void SetTopOfRelativePoint(UIElement uiElement, double top) {

            var renderOrigin = (Point)uiElement.GetValue(RelativeOriginProperty);

            SetTop(uiElement, top - renderOrigin.Y * uiElement.RenderSize.Height);
        }

        #region HorizontalProportion

        public static readonly DependencyProperty HorizontalProportionProperty =
            DependencyProperty.RegisterAttached("HorizontalProportion", typeof(double), typeof(RelativeCanvas),
                new FrameworkPropertyMetadata(double.NaN,
                    OnHorizontalProportionChanged));

        public static double GetHorizontalProportion(DependencyObject d) {
            return (double)d.GetValue(HorizontalProportionProperty);
        }

        public static void SetHorizontalProportion(DependencyObject d, double value) {
            d.SetValue(HorizontalProportionProperty, value);
        }

        private static void OnHorizontalProportionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var oldHorizontalProportion = (double)e.OldValue;
            var newHorizontalProportion = (double)d.GetValue(HorizontalProportionProperty);
        }

        #endregion

        #region VerticalProportion

        public static readonly DependencyProperty VerticalProportionProperty =
            DependencyProperty.RegisterAttached("VerticalProportion", typeof(double), typeof(RelativeCanvas),
                new FrameworkPropertyMetadata(double.NaN,
                    OnVerticalProportionChanged));

        public static double GetVerticalProportion(DependencyObject d) {
            return (double)d.GetValue(VerticalProportionProperty);
        }

        public static void SetVerticalProportion(DependencyObject d, double value) {
            d.SetValue(VerticalProportionProperty, value);
        }

        private static void OnVerticalProportionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var oldVerticalProportion = (double)e.OldValue;
            var newVerticalProportion = (double)d.GetValue(VerticalProportionProperty);
        }

        #endregion

        #region RelativeOrigin

        public static readonly DependencyProperty RelativeOriginProperty =
            DependencyProperty.RegisterAttached("RelativeOrigin", typeof(Point), typeof(RelativeCanvas),
                new FrameworkPropertyMetadata(new Point(0.5, 0.5),
                    OnRelativeOriginChanged));

        public static Point GetRelativeOrigin(DependencyObject d) {
            return (Point)d.GetValue(RelativeOriginProperty);
        }

        public static void SetRelativeOrigin(DependencyObject d, Point value) {
            d.SetValue(RelativeOriginProperty, value);
        }

        private static void OnRelativeOriginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var oldRelativeOrigin = (Point)e.OldValue;
            var newRelativeOrigin = (Point)d.GetValue(RelativeOriginProperty);
        }

        #endregion

    }
}

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s