Usando un ContentControl como hijo de un Control SemanticZoom en XAML+C# de Windows 8

El Zoom Semantico de Windows 8 permite que los usuarios puedan hacer zoom sobre el contenido de una aplicación. Esto no significa que el Zoom esté centrado en pixeles como en normal, sino que el zoom está centrado en contenido. Veamos un ejemplo de esto:

SemanticZoom

El zoom semántico permite tener dos vistas de un mismo contenido. Una vista de zoom cercano y otra vista de zoom lejano, ambas se puede intercambiar a través de interacciones de la plataforma como teclado, ratón y táctil. El código XAML correspondiente a este control sería este:

<SemanticZoom>
    <SemanticZoom.ZoomedInView>
        <GridView />
    </SemanticZoom.ZoomedInView>
    <SemanticZoom.ZoomedOutView>
        <GridView />
    </SemanticZoom.ZoomedOutView>
</SemanticZoom>

 

Este control tiene dos propiedades, ZoomedInView y ZommedOutView que son de tipo ISemanticZoomInformation. Esta interfaz es que permite la comunicación con el sistema de Zoom semántico para así poder cambiar la vista en cualquier momento que el usuario lo desee. Los dos únicos controles que implementan esa interfaz son el GridView y el ListView.

clip_image002

clip_image004

Aquí se puede ver un ejemplo de aplicación con dos vistas en zoom semántico.

Pero, ¿qué pasa si quiero usar otro control que no sea un GridView como hijo del Zoom Semántico?

SemanticZoomHost

Este control permite tener cualquier árbol de objetos en xaml y que a su vez pueda ser hijo de cualquiera de las dos propiedades del SematicZoom. Esta clase implementa la interfaz ISemanticZoomInformation, pero permite que la información de zoom se transadle a uno de sus hijos.

image

De esta manera se puede no tener un GridView únicamente sino que se puede tener cualquier tipo control y dentro de ese árbol un control GridView.

Ejemplo

<SemanticZoom>
    <SemanticZoom.ZoomedInView>
        <controls:SemanticZoomHost>
            <StackPanel>
                <TextBlock Text="Titulo"></TextBlock>
                <GridView controls:SemanticZoomHost.SemanticZoomHost="true"></GridView>
            </StackPanel>
        </controls:SemanticZoomHost>
    </SemanticZoom.ZoomedInView>
    <SemanticZoom.ZoomedOutView>
        <GridView />
    </SemanticZoom.ZoomedOutView>
</SemanticZoom>

 

¿Cómo está implementado SemanticZoomHost?

La idea que hay detrás es muy sencilla, una clase que herede de ContentControl, para que así pueda tener hijos en el árbol visual, y que implemente la interfaz ISemanticZoomInformation. La clase en sí no hace nada con la implementación de la interfaz, es decir, que simplemente traslada las llamadas al control que realmente implementa ISemanticZoomInformation. Es por eso que el control SemanticZoomHost tiene una propiedad de tipo ISematicZoomInformation que será el control que manejará el zoom semántico.

public ISemanticZoomInformation ISemanticZoomInformationHost
{
    get { return (ISemanticZoomInformation)GetValue(ISemanticZoomInformationHostProperty); }
    set { SetValue(ISemanticZoomInformationHostProperty, value); }
}

public static readonly DependencyProperty ISemanticZoomInformationHostProperty =
    DependencyProperty.Register(
        "ISemanticZoomInformationHost",
        typeof(ISemanticZoomInformation),
        typeof(SemanticZoomHost),
        new PropertyMetadata(null));

 

Attached Property

Esa propiedad tiene que ser establecida de manera automática, es decir, que de alguna manera el sistema tiene que buscar que control del árbol visual es el encargado de establecer esa propiedad. Es en este punto donde las Attached property entran en acción para que el propio control SemanticZoomHost defina una attached property llamada SemanticZoomHost de tipo booleana, que será utilizada para marcar que control en el árbol visual es el encargado de ser el receptor de la información del zoom semántico.

public static bool GetSemanticZoomHost(DependencyObject obj)
{
    return (bool)obj.GetValue(SemanticZoomHostProperty);
}

public static void SetSemanticZoomHost(DependencyObject obj, bool value)
{
    obj.SetValue(SemanticZoomHostProperty, value);
}

public static readonly DependencyProperty SemanticZoomHostProperty =
    DependencyProperty.RegisterAttached(
        "SemanticZoomHost",
        typeof(bool),
        typeof(SemanticZoomHost),
        new PropertyMetadata(false));

 

Buscando ese control en el árbol visual

La última tarea que tiene la clase es buscar los controles en el árbol visual que tengan esa propiedad a true y establecer esa propiedad. Como el control es del tipo ContentControl, tiene el método OnApplyTemplate, pero que en este caso no se puede usar porque el control en sí se ha cargado pero no todos los hijos, así que hay que usar el evento Loaded para saber cuándo se ha terminado de cargar todo el árbol visual de todos los hijos.

void OnSemanticZoomHostLoaded(object sender, RoutedEventArgs e)
{
    List<DependencyObject> all = ControlTreeHelper.GetAllControlsByType<DependencyObject>(this);
    foreach (var item in all)
    {
        if (SemanticZoomHost.GetSemanticZoomHost(item))
        {
            ISemanticZoomInformation semanticZoom = (ISemanticZoomInformation)item;
            semanticZoom.IsActiveView = isActiveView;
            semanticZoom.IsZoomedInView = isZoomedView;
            semanticZoom.SemanticZoomOwner = owner;

            this.ISemanticZoomInformationHost = semanticZoom;
            break;
        }
    }
}

 

El resto de la implementación son los métodos de la interfaz ISemanticZoomInformation.

Implementación

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace SematicZoomDemo.Controls
{
    public class SemanticZoomHost : ContentControl, ISemanticZoomInformation
    {
        public SemanticZoomHost()
        {
            Loaded += OnSemanticZoomHostLoaded;
        }

        void OnSemanticZoomHostLoaded(object sender, RoutedEventArgs e)
        {
            List<DependencyObject> all = ControlTreeHelper.GetAllControlsByType<DependencyObject>(this);
            foreach (var item in all)
            {
                if (SemanticZoomHost.GetSemanticZoomHost(item))
                {
                    ISemanticZoomInformation semanticZoom = (ISemanticZoomInformation)item;
                    semanticZoom.IsActiveView = isActiveView;
                    semanticZoom.IsZoomedInView = isZoomedView;
                    semanticZoom.SemanticZoomOwner = owner;

                    this.ISemanticZoomInformationHost = semanticZoom;
                    break;
                }
            }
        }

        public void CompleteViewChange()
        {
            ISemanticZoomInformationHost.CompleteViewChange();
        }

        public void CompleteViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination)
        {
            ISemanticZoomInformationHost.CompleteViewChangeFrom(source, destination);
        }

        public void CompleteViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination)
        {
            ISemanticZoomInformationHost.CompleteViewChangeTo(source, destination);
        }

        public void InitializeViewChange()
        {
            ISemanticZoomInformationHost.InitializeViewChange();
        }

        public void MakeVisible(SemanticZoomLocation item)
        {
            ISemanticZoomInformationHost.MakeVisible(item);
        }


        public void StartViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination)
        {
            ISemanticZoomInformationHost.StartViewChangeFrom(source, destination);
        }

        public void StartViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination)
        {
            ISemanticZoomInformationHost.StartViewChangeTo(source, destination);
        }

        public bool IsActiveView
        {
            get
            {
                return ISemanticZoomInformationHost.IsActiveView;
            }
            set
            {
                if (ISemanticZoomInformationHost == null)
                {
                    isActiveView = value;
                }
                else
                {
                    ISemanticZoomInformationHost.IsActiveView = value;
                }
            }
        }

        public bool IsZoomedInView
        {
            get
            {
                return ISemanticZoomInformationHost.IsZoomedInView;
            }
            set
            {
                if (ISemanticZoomInformationHost == null)
                {
                    isZoomedView = value;
                }
                else
                {
                    ISemanticZoomInformationHost.IsZoomedInView = value;
                }
            }
        }

        public SemanticZoom SemanticZoomOwner
        {
            get
            {
                return ISemanticZoomInformationHost.SemanticZoomOwner;
            }
            set
            {
                if (ISemanticZoomInformationHost == null)
                {
                    owner = value;
                }
                else
                {
                    ISemanticZoomInformationHost.SemanticZoomOwner = value;
                }
            }
        }

        private SemanticZoom owner;
        private bool isZoomedView;
        private bool isActiveView;

        public ISemanticZoomInformation ISemanticZoomInformationHost
        {
            get { return (ISemanticZoomInformation)GetValue(ISemanticZoomInformationHostProperty); }
            set { SetValue(ISemanticZoomInformationHostProperty, value); }
        }

        public static readonly DependencyProperty ISemanticZoomInformationHostProperty =
            DependencyProperty.Register(
                "ISemanticZoomInformationHost",
                typeof(ISemanticZoomInformation),
                typeof(SemanticZoomHost),
                new PropertyMetadata(null));

        public static bool GetSemanticZoomHost(DependencyObject obj)
        {
            return (bool)obj.GetValue(SemanticZoomHostProperty);
        }

        public static void SetSemanticZoomHost(DependencyObject obj, bool value)
        {
            obj.SetValue(SemanticZoomHostProperty, value);
        }

        public static readonly DependencyProperty SemanticZoomHostProperty =
            DependencyProperty.RegisterAttached(
                "SemanticZoomHost",
                typeof(bool),
                typeof(SemanticZoomHost),
                new PropertyMetadata(false));
    }
}

 

 

Conclusión

En SematicZoom es un control muy útil a la hora de hacer zoom semántico en una aplicación de Windows 8, pero desde mi punto de vista es una limitación que solamente se pueda establecer un ListView o un GridView. En muchas ocasiones los layouts que se puede obtener son muy complejos.

ControlTreeHelper

Como podéis ver en el código se utiliza una clase llamada ControlTreeHelper. Esta clase fue diseñada para usarse en WPF (Windows Presentation Foundation), ¡qué tiempos aquellos!.

La clase contiene métodos que nos permite recorrer el árbol visual de manera cómoda, paso a comentar algunos de esos métodos.

  • List<T> GetAllControlsByType<T>(DependencyObject root): Obtiene todos los controles hijos filtrados por el tipo T y los devuelve en una lista de T.
  • T FindUniqueParentControl<T>(DependencyObject leaf): Busca un padre directo en el arbol visual de tipo T.
  • T FindNameInVisualTree<T>(DependencyObject root, string name): Busca un elemento en el arbol visual de tipo T con el nombre pasado por parametro.

El código de ejemplo se puede descargar de aquí.

Luis Guerrero.