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.

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.