Custom Control WPF I

sábado, 15 de septiembre de 2012

Muchas veces, realizar una personalización de un botón, o de otro de los controles que encontramos en el namespace System.Windows para utilizar directamente en el marcado XAML, resulta un trabajo bastante tedioso y a veces complejo personalizar uno de estos controles si no tienes mucha habilidad con el diseño, y no siempre cumple con las expectativas que tenemos.

Con este artículo, mi intención es crear un control, que contenga un texto, una imagen, y un contador, el cual lo vamos a utilizar a modo de ejemplo para contar los clicks que hagamos en el control.

Crear un proyecto WPF

Creamos un nuevo proyecto de WPF, al que le vamos a agregar dos nuevos elementos, el primero un diccionario de recursos, y el otro un control personalizado.

Agregar nuevo elemento

Control personalizado

Este control, no deja de ser una clase que implementa Control que representa la clase base de los elementos de la interfaz de usuario (UI) que utilizan un control para definir su apariencia de System.Windows.Controls.ControlTemplate.

Lo primero es agregar varias DependecyProperties, una para el icono, otra para el texto del control, y la última el contador de clicks.

        public static readonly DependencyProperty TextProperty;
        public static readonly DependencyProperty IconProperty;
        public static readonly DependencyProperty ClicksProperty;

        public object Icon
        {
            get { return (Image)GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }
        public String Text
        {
            get { return (String)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
        public String Clicks
        {
            get { return (String)GetValue(ClicksProperty); }
            set { SetValue(ClicksProperty, value); }
        }

Una vez agregadas las distintas propiedades, las registramos en el constructor.

        static CustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
            CustomControl.TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(CustomControl), new UIPropertyMetadata(null));
            CustomControl.IconProperty = DependencyProperty.Register("Icon", typeof(Image), typeof(CustomControl), new UIPropertyMetadata(null));
            CustomControl.ClicksProperty = DependencyProperty.Register("Clicks", typeof(String), typeof(CustomControl), new UIPropertyMetadata(null));
        }

Finalmente tenemos el control listo para ser usado, salvo por un detalle, que no está especificado cómo se disponen los distintos objetos.

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

namespace WpfApplication1
{
    public class CustomControl : Control
    {
        public static readonly DependencyProperty TextProperty;
        public static readonly DependencyProperty IconProperty;
        public static readonly DependencyProperty ClicksProperty;

        public object Icon
        {
            get { return (Image)GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }
        public String Text
        {
            get { return (String)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
        public String Clicks
        {
            get { return (String)GetValue(ClicksProperty); }
            set { SetValue(ClicksProperty, value); }
        }
        static CustomControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
            CustomControl.TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(CustomControl), new UIPropertyMetadata(null));
            CustomControl.IconProperty = DependencyProperty.Register("Icon", typeof(Image), typeof(CustomControl), new UIPropertyMetadata(null));
            CustomControl.ClicksProperty = DependencyProperty.Register("Clicks", typeof(String), typeof(CustomControl), new UIPropertyMetadata(null));
        }
    }
}

Diccionario de recursos

El diccionario de recursos, nos permite crear plantillas y estilos globales o específicos, para los controles que introducimos en la aplicación, además, nos permite añadir valores como colores, brochas, fuentes, tamaños, y características comunes entre controles, lo cual nos permite reutilizar recursos, con el fin de dar un formato homogéneo a nuestra aplicación. Más información en MSDN Microsoft.

Una vez agregado el diccionario, lo primero que hay que realizar es vincularlo a la aplicación. Blend Studio lo suele hacer de forma automática, pero si trabajas desde Visual Studio, lo normal es hacerlo uno mismo, ya que si utilizas varias hojas de recursos, el orden de estas es fundamental, ya que en el marcado, cuando agregas varios componentes en la misma posición, prevalece y se superpone el que se encuentra en último lugar.

Bien, para agregar un nuevo diccionario, abrimos el App.xaml, y ahí agregamos a los recursos el diccionario.

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dictionary.xaml"/>
                <!--<ResourceDictionary Source="Otro.xaml"/> Este prevalecería sobre el anterior-->
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Una vez realizado esto, los recursos estarán disponibles de forma dinámica para nuestra aplicación.

A continuación en el diccionario de recursos agregamos un nuevo estilo para personalizar el control que hemos creado, para que podamos crear este estilo primero debemos agregar el namespace al marcado, para lo que voy a utilizar "local" como prefijo.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApplication1">
</ResourceDictionary>

Después agregamos un par de colores, y sus respectivas brochas, para que podamos dar color al control.

    <Color x:Key="ColorTwitter">#FF77AADB</Color>
    <Color x:Key="ColorWhite">#FFFDFDFD</Color>
    <Color x:Key="ColorBlue">#FF24348C</Color>
    <SolidColorBrush x:Key="BrushTwitter" Color="{StaticResource ColorTwitter}"/>
    <SolidColorBrush x:Key="BrushWhite" Color="{StaticResource ColorWhite}"/>
    <SolidColorBrush x:Key="BrushBlue" Color="{StaticResource ColorBlue}"/>

Y empezamos a crear el estilo, para componer el objeto, voy a utilizar un grid, con dos columnas, y dos filas, en la primera columna insertaré a doble fila una imagen. En la otra columna, en una fila un texto, y en la otra fila un contador de clicks.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApplication1">

    <Color x:Key="ColorTwitter">#FF77AADB</Color>
    <Color x:Key="ColorWhite">#FFF3F3F3</Color>
    <Color x:Key="ColorBlue">#FF24348C</Color>
    <SolidColorBrush x:Key="BrushTwitter" Color="{StaticResource ColorTwitter}"/>
    <SolidColorBrush x:Key="BrushWhite" Color="{StaticResource ColorWhite}"/>
    <SolidColorBrush x:Key="BrushBlue" Color="{StaticResource ColorBlue}"/>

    <Style x:Key="CustomCtrlv1" TargetType="{x:Type local:CustomControl}">
        <Setter Property="Background" Value="{StaticResource BrushTwitter}"/>
        <Setter Property="Foreground" Value="{StaticResource BrushWhite}"/>
        <Setter Property="BorderBrush" Value="{StaticResource BrushBlue}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Margin" Value="7,3"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomControl}">
                    <Border x:Name="bd" Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid Margin="{TemplateBinding Margin}">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <ContentPresenter x:Name="img" Grid.Column="0" Grid.Row="0"
                                              Grid.RowSpan="2" ContentSource="Icon"
                                              Width="30" Height="30" Margin="5"/>
                            <TextBlock x:Name="txt" Grid.Column="1" Grid.Row="0"
                                       VerticalAlignment="Center" Text="{TemplateBinding Text}"
                                       Foreground="{TemplateBinding Foreground}"
                                       FontWeight="Bold"/>
                            <TextBlock x:Name="clicks" Grid.Column="1" Grid.Row="1"
                                       VerticalAlignment="Center" Text="{TemplateBinding Clicks}"
                                       Foreground="{TemplateBinding Foreground}"
                                       FontStyle="Italic"/>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="{DynamicResource BrushWhite}"/>
                            <Setter Property="Foreground" Value="{DynamicResource BrushBlue}"/>
                            <Setter Property="BorderBrush" Value="{DynamicResource BrushTwitter}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Aprovechando que estaba creando el estilo, he agregado un Trigger para cambiar la apariencia del control cuando el ratón se encuentre encima. Así tendremos dos estados distintos cuando el control tiene encima el cursor.

El siguiente paso es insertar el control dentro de la aplicación, para ello continúo en el siguiente artículo.

Deja un comentario

Buscar

Search