SelectedItem, SelectedValue

Los ComboBox, las ListBox y toda esa recua de controles tan útiles son unas verdaderas bestias. En realidad, todos los ItemsControl son tan bellos como complejos.

No entender qué cojopios hacen es una de las razones por la que creamos chorricode totalmente vomitivo. “ItemsControl”, quédate con el nombre. ¡Hay que estudiárselos bien!

A nivel de usuario hay básico entender este par de propiedades:

  • SelectedItem: El elemento seleccionado. Si tenemos un Binding TwoWay a esta propiedad debemos cuidar muy mucho ciertas cosas. Lo más importante:

    El elemento enlazado con el “binding” puede no existir en la lista. Si esto se produce, visualmente no se seleccionará NADA. Es decir, ningún elemento se marcará.

  • SelectedValue: Valor seleccionado. No importa si el elemento no está en la lista. Se selecciona y punto. Queda más claro si digo que en un combo box, el Popup desplegable lleva la lista de elementos, pero luego tiene un elemento especial, que es el que aparece en la cajita del combo cuando está cerrado. El SelectedValue se pondrá ahí. Lo malo es que no siempre es posible representar el elemento (puede ser un elemento no “renderizable”.

IMPORTANTE: Para que la cosa funcione es imprescindible sobreescribir los métodos Equals en los elementos que vayan a ser representados en el ItemsControl. Es lógico, puesto que, ¿cómo va a saber sino cuál elemento se selecciona de la lista si no existe una manera de comprobar cuándo son el mismo elemento?

WARNING: El método Equals por defecto compara referencias, por lo que si son la misma referencia, el SelectedItem furrula fairly good, pero cuando la instancia te la sacas tú de la manga, haces un clone y vete tú a saber qué historias, el Equals es obligatorio si quieres que todo se sincronice solito.

¡Esos Equals tan impopulares a los que nadie les hace caso son importantísimos! Úsalos o tendré que sacar el látigo de dar.

Duck Typing FTW

Para que no se te olvide, golferas, si quieres programar como Dios manda deberías hacerlo siempre intentando programar “contra” interfaces, aunque no es una expresión que me guste mucho eso de “contra”, pero así todo el mundo lo entiende.

Para facilitarnos la tarea existe una técnica llamada Duck Typing.

“Si anda como un pato, nada como un pato y vuela como un pato, entonces es un pato”.

En pocas palabras Duck Typing es conseguir tratar como un objeto fuese algo que realmente no lo es, aunque cumple con sus requisitos. Esto se traduce a tener tener un tipo que no implementa cierta interfaz, pero lo tratamos como si la implementase.

El gran Juan María Hernández (@gulnor) publica en su blog publica un artículo especialmente interesante donde explica muy bien qué es y cómo funciona, además de exponer una pequeña implementación de andar por casa ayudarnos a entender cómo funciona por dentro.

¿Cómo lo consigue? Con reflexión, invocando atributos y métodos como si realmente estuviésemos trabajando con el tipo que queremos.

Sin duda, un concepto revolucionario con lo que podemos crear código más abstracto  y menos acoplado, que es de lo que se trata.

Por cierto, si no quieres devanarte la sesera para hacerte tu propia implementación de esta , échale un vistazo al DynamicProxy o al aparentemente bueno ImpromptuInterface (http://code.google.com/p/impromptu-interface/).

Venga, a probarlo ya, ¡jodebles!

Databinding to Structs

Well, this is going to be rude: Binding to struct types can be a tricky. You will absolutely love when the UI just ignores every change. It refuses to update and eventually you’ll get mad about this, wondering why those nice bindings to Paddings or Margins are ignored. It’s kind of frustrating.

This boils down the nature of structs. They are value types and they’re taken as a whole by the dependency property mechanism.

When you define a DP that carries a struct, any binding to a child attribute that composes that struct reflects the actual value of that member, but ONLY when the struct is replaced by another “instance” of the struct (another value). Changing its attributes will not notify any change (Property Changed callbacks won’t be called).

For instance, you have a DP with a Point and a binding link this:

Text=”{Binding Path=MyPoint.X}”

Suppose that the X value of the struct is modified the some other binding (or whatever). You may expect the Text to reflect the X value anytime it is changed, but no way! WPF will blow you off, sticking out its long and complex tongue.

This is (not easily) solved replacing the value of the struct whenever you want the change to be reflected in the UI. “What? you mean that I have to care about changes in every member of the struct in order to have the bindings fresh like my cotton underwear”. Yes, that’s it!

Pretty annoying! but this is the price you have to pay for using those cute simplistic and handy structs!

Don’t forget it. Never! You sweated too much because of this Sonrisa! I hope you finally understood, Suppa JMN!

Y todo esto para tirarme el pegote de escribir en Inglés, ¡copón! Bueno, también es verdad que si algún lobo de mar angloparlante viene a mi blog por alguna casualidad de la vida, quizá le guste entender algo, ¿no?

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

    }
}

Quitar comentarios de cabecera en código fuente

No hay nada más aburrido y cansino que quitar las cabeceras al código fuente. Hay peña que se dedica a ponerlas para decir “esto lo hemos hecho en tal empresa y mira qué enlaces más bonitos, y copyright y bla bla bla”. O sea, una pollez. El archivo de código fuente debe ser limpio, ¡código sin moñas! Pero es que además existen buenas razones para no meter brocilla:

Un merge entre 2 archivos con el código igualito menos los comentarios de cabecera. Te das cuenta de que todos los archivos tienen conflictos, ¿por qué? POR UN JODIDO COMENTARIO DE CABECERA.

Y lo peor es que da conflictos en todo. ¿Cómo discriminas los archivos que realmente contienen conflictos de los que no? 

Ayer me pasó. Y te dan ganas de pegarte un tiro.

Así es que aquí tienes la herramienta que te hará la vida más fácil.

License Header Manager for Visual Studio

Desde el Explorador de la Solución hay un menú contextual muy práctico:

image

Y ahora es cuando digo, oh really yeah!