Una Attached Property (propiedad asociada) es un concepto definido por XAML. Las propiedades asociadas están destinadas a utilizarse como un tipo de propiedad global configurable en cualquier objeto. En WPF, las propiedades asociadas se definen habitualmente como una forma especializada de propiedad de dependencia que no tiene el "contenedor" de propiedad convencional.
La mayoría de las propiedades asociadas que existen en tipos WPF se implementan como Dependency Properties (propiedades de dependencia). El escenario más típico donde WPF define una propiedad asociada es cuando un elemento primario admite una colección de elementos secundarios, y también implementa un comportamiento donde las características del comportamiento se indican individualmente para cada elemento secundario. Por ejemplo, la propiedad Grid.Row o Grid.Column se definen en cualquier objeto secundario de un Grid para establecer su posición en el mismo.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="1" Width="Auto" Height="26"/>
</Grid>
En el ejemplo que se propone en este post, se utilizarán las propiedades asociadas con otro fin:
El tipo que define la propiedad asociada se ha diseñado de modo que pueda manejar o administrar los objetos que establecen los valores de la propiedad asociada. El tipo tiene acceso de esta forma al objeto, obtiene los valores de sus propiedades y actúa sobre esos valores.
Para este fin se ha diseñado una clase llamada TextBoxBehavior, en la que definimos propiedades asociadas como Mask (máscara de entrada), WatermarkText (marca de agua) y otras. Un ejemplo de declaración sería:
public static readonly DependencyProperty WatermarkTextProperty = DependencyProperty.RegisterAttached("WatermarkText", typeof(string),typeof(TextBoxBehavior), new FrameworkPropertyMetadata(WaterMarkTextChangedCallback));
private static void WaterMarkTextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox _this = (d as TextBox);
if (_this != null) textBox.Loaded += textBox_Loaded;
}
Podemos ver que en el evento de cambio de la propiedad asociada, aprovechamos para suscribirnos al manejador del evento Loaded del TextBox. De esta forma, conseguimos añadir funcionalidad al mismo sin tener que derivar el control. En el evento Loaded del TextBox asignamos el valor de la propiedad asociada WaterMarkText a la propiedad Text de un objeto TextBlock que forma parte de la plantilla definida para nuestros objetos TextBox:
static void textBox_Loaded(object sender, RoutedEventArgs e)
{
TextBox textBox = (sender as TextBox);
if (textBox != null)
{
if (GetWatermarkText(textBox) != string.Empty)
{
//Accedemos al textBlock contenido en el Template del TextBox, y asignamos su propiedad Text
DependencyObject dep=VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(textBox, 0), 1);
((TextBlock)dep).Text = GetWatermarkText(textBox);
}
}
}
La definición de plantilla del TextBox, nos muestra el control TextBlock en su interior y cuando debemos mostrarlo u ocultarlo:
<!-- Estilo de TEXTBOX por defecto-->
<Style TargetType="{x:Type TextBox}">
<Setter Property="FontSize" Value="12" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<ScrollViewer x:Name="PART_ContentHost" Background="White" Height="23" />
<TextBlock x:Name="textBlock" Height="20" Margin="5" VerticalAlignment="Center" FontSize="14" Opacity="0.4" Visibility="Hidden" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Visibility" TargetName="textBlock" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
De la misma forma que hemos implementado esta funcionalidad, podríamos hacer cosas mucho más complejas añadiendo más propiedades asociadas y/o suscribiéndonos a otros eventos del objeto que queramos administrar.