[Curso] WPF para programadores de Windows Forms 9 : Templates

[Curso] WPF para programadores de Windows Forms 9 : Templates

En el post anterior comentábamos como en WPF existen el árbol visual y el árbol lógico que nos permite definir como un control se comporta y como se dibuja en la pantalla, pues bien lo que vamos a ver ahora es como se pueden modificar el árbol visual y cómo podemos crear un árbol visual para una clase que no tiene árbol visual.

Todas los tipos de plantillas heredan de la misma clase, FrameworkTemplate que permite definir plantillas para diferentes tipos de controles.

image

Si nos fijamos en el diagrama, tenemos tres tipos de plantillas que definir:

· DataTemplate: permite definir una plantilla de datos, es decir asociar un árbol visual a una clase o tipo. Podemos definir cómo se va a visualizar la clase Employee.

· ControlTemplate: permite cambiar la plantilla visual de un control de WPF.

· ItemsPanelTemplate: permite cambiar la platilla de un control que es una lista de elementos, como por ejemplo un ListBox.

· HierarchicalDataTemplate: permite definir una plantilla de datos para una estructura jerárquica, como por ejemplo un árbol y asociar este a un TreeView.

DataTemplate

La clase DataTemplate permite definir una plantilla visual para un tipo cualquiera. Normalmente las plantillas se definen en XAML. Si tenemos un tipo como este:

public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Company { get; set; }
}

Si lo establecemos como contenido de algún control WPF lo que hará será llamar al ToString() de este objeto para dibujarlo, podemos sobrescribir ToString() para darle otro sentido pero no podemos personalizar como el objeto se dibuja en pantalla.

Lo que podemos hacer es definir su plantilla visual mediante la clase DataTemplate en xaml, normalmente vamos a tener que definir nuestra DataTemplate en un diccionario de recursos o podemos hacerlo directamente en la propiedad ItemTemplate de algunos controles.

<DataTemplate DataType="{x:Type local:Employee}">
   <StackPanel>
       <TextBlock Text="{Binding Path=Name}" />
   </StackPanel>
</DataTemplate>

Como se puede observar en este ejemplo hemos definido un DataTemplate dentro del diccionario de recursos de Window, pero no hemos establecido la propiedad x:Key necesaria para poder acceder a ese valor en el diccionario, sino que lo único que hemos hecho es definir la propiedad DataType de la clase DataTemplate. Al hacer esto WPF automáticamente utilizará esta plantilla para dibujar el tipo Employee cuando se lo encuentre dentro del ámbito de la ventana. Esto es extremadamente útil, porque podemos definir esta plantilla en un ResourceDiccionary a nivel de Application y hacer que esté disponible para toda la aplicación.

Para asociar propiedades de la clase para mostrarla se realiza con un binding como se puede ver en el TextBlock, no quiero centrarme en eso ahora sino simplemente mostrar cuales son las capacidades de personalización de la DataTemplate.

Si hubiéramos utilizado un ContentControl para dibujar nuestro objeto Employe podríamos haber definido la plantilla directamente en la propiedad ContentTemplate, sin necesidad de asociar el DataType o establecer el x:Key.

<ContentControl>
    <ContentControl.ContentTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Path=Name}" />
            </StackPanel>
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

 

ControlTemplate

ControlTemplate nos permite definir o redefinir la plantilla de un control, es decir de cualquier control que herede de Control, que es donde está definida la propiedad Template. La manera de proceder es igual, para un Button tenemos:

<ControlTemplate TargetType="{x:Type Button}" x:Key="ButtonControlTemplate">
    <Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" >
        <Border BorderThickness="3" BorderBrush="red">
            <ContentPresenter />
        </Border>
    </Microsoft_Windows_Themes:ButtonChrome>
</ControlTemplate>

 

Lo que hemos hecho en este ejemplo es redefinir la plantilla de Button para tener esto:

clip_image004

Como se puede observar lo que tenemos en pantalla no se parece a un botón, no tiene el aspecto que esperamos de un botón, pero si preguntamos su tipo es un Button, así que lo que hemos conseguido es modificar su árbol visual sin necesidad de heredad de Button y generar un tipo nuevo, simplemente cambiado su plantilla.

Este cambio de plantilla normalmente viene integrado en un estilo (Style) pero eso lo veremos en otro post.

ItemsPanelTemplate

Esta clase permite definir la plantilla para un control de tipo ItemsControl, como por ejemplo TreeView, TabControl, etc. En este ejemplo vamos a cambiar la plantilla del ListBox para hacerlo con UniformGrid.

<ItemsPanelTemplate x:Key="ItemsPanel">
    <UniformGrid IsItemsHost="True">
    </UniformGrid>
</ItemsPanelTemplate>

Aquí podemos ver el resultado:

clip_image006clip_image008

Las posibilidades son infinitas, pues podemos personalizar cualquier control como nosotros queramos sin necesidad de tener que tocar una sola línea de código simplemente con un poco de xaml.

HierarchicalDataTemplate

Esta plantilla basada en un DataTemplate permite generar plantillas jerárquicas para nuestros tipos de datos, como está pensada para usarse con un tipo de datos, primero vamos a definir un tipo de dato en árbol y generar algunos elementos.

public class Node
{
    public string Name { get; set; }
    public ObservableCollection<Node> Items { get; set; }

    public Node()
    {
        Items = new ObservableCollection<Node>();
    }
}

Esta clase Node contiene un nombre una colección de elementos y una colección de elementos del mismo tipo en Items. Podemos definir una plantilla jerárquica como esta:

<HierarchicalDataTemplate ItemsSource="{Binding Path=Items}" x:Key="NodeTemplate">
    <TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>

 

Tenemos que definir cuál va a ser la propiedad que se va a usar como origen de datos para los Items que se van a generar, la propiedad ItemsSource nos permite definir mediante un Binding que la propiedad Items, de nuestra clase Node, será la encargada de generar los elementos hijos. Por cada uno de las clases Node que nos encontremos vamos a generar un TextBlock en el que vamos a asociar la propiedad Text del control, con un Binding, a la propiedad Name de nuestra clase Node.

Ahora tenemos que generar algunos elementos de ejemplo:

private Node GenerateRandomNodes()
{
    Node n = new Node();
    n.Name = "Raiz";
    for (int i = 0; i < 20; i++)
    {
        Node tmp = new Node();
        tmp.Name = i.ToString();
        n.Items.Add(tmp);
        for (int x = 0; x < 10; x++)
        {
            Node tmp2 = new Node();
            tmp2.Name = string.Format("{0} - {1}", i, x);
            tmp.Items.Add(tmp2);
        }
    }
    return n;
}

 

Una vez ejecutado este es el resultado:

clip_image010

Resumiendo

Como se puede observar una de las cosas buenas de tener un árbol visual y un lógico es que podemos redefinir como nuestro control o clase se visualiza en pantalla simplemente generando una plantilla. Las plantillas son el mecanismo natural por el cual se pueden personalizar los controles en WPF y son la principal herramienta de los diseñadores para personalizar.

Avanzado

Para terminar el artículo me gustaría hablar sobre un tema que a todos se nos plantea en algún momento, generar plantillas de manera dinámica, por código. En WPF, se pueden generar plantillas dinámicamente, pero el tema es algo complicado y abstracto. Veamos un ejemplo.

Vamos a intentar definir una plantilla dinámica para nuestro tipo de dato Employee por código, si empezamos a generar código vemos que podemos generar una instancia de la clase DataTemplate pasando por parametro el tipo del objeto al que queremos generar la plantilla, la propiedad VisualTree de tipo FrameworkElementFactory nos permite definir como queremos que sea nuestra plantilla.

Todo el trabajo está en esta clase FrameworkElementFactory, para definir los elementos tenemos que especificar cómo se construirán, así que siguiendo nuestro ejemplo de DataTemplate, en el que tenemos un StackPanel y dentro un TextBlock así es como se definiría en código:

   1: private DataTemplate GenerateDataTemplate()
   2: {
   3:     DataTemplate dt = new DataTemplate(typeof(Employee));
   4:     FrameworkElementFactory stackPanel = new FrameworkElementFactory();
   5:     stackPanel.Type = typeof(StackPanel);
   6:     stackPanel.SetValue(StackPanel.BackgroundProperty, new SolidColorBrush(Colors.Yellow));
   7:     FrameworkElementFactory text = new FrameworkElementFactory(typeof(TextBlock));
   8:     text.SetBinding(TextBlock.TextProperty, new Binding("Name"));
   9:     stackPanel.AppendChild(text);
  10:     text = new FrameworkElementFactory(typeof(TextBlock));
  11:     text.SetBinding(TextBlock.TextProperty, new Binding("Company"));
  12:     stackPanel.AppendChild(text);
  13:     text = new FrameworkElementFactory(typeof(TextBlock));
  14:     text.SetBinding(TextBlock.TextProperty, new Binding("Age"));
  15:     stackPanel.AppendChild(text);
  16:     dt.VisualTree = stackPanel;
  17:     return dt;
  18: }

La idea es establecer en cada uno de los FrameworkElementFactory las DependencyProperties, Bindings y Childrens que definen a un control.

El código fuente lo podéis descargar de aquí http://www.luisguerrero.net/downloads/templates.zip

Saludos. Luis.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *