Behaviors en WPF

Con la llegada de Expression Blend 3 tenmos un nuevo concepto en nuestros desarrollos que nos va a permitir que la unión entre desarrolladores y diseñadores sea todavía mejor.

Los behaviors son piezas de código que los diseñadores pueden asociar a un objeto simplemente arrastrándolos con el ratón.Blend ya nos trae una serie de behaviors que podemos utilizar ya, además de poder descargar unos cuantos mas de la pagina Expression Gallery’s Behaviors, pero además podemos construir nuestros behaviors  para acelerar nuestros desarrollos. Con los behaviors  conseguimos añadir interactividad sin escribir ningún código con lo que los diseñadores pueden incluirlos en sus diseños.

Para crear nuestros behaviors debemos de crear una clase que herede de Behavior o Behavior<T>, estas clases están implementadas en el assembly System.Windows.Interactivity.dll que añadiremos a nuestro proyecto, pero como siempre un ejemplo sencillo para ver como se crean, en este ejemplo vamos a crear un behaivour para los objetos TextBox a los cuales les vamos a añadir la funcionalidad de marca de agua.

Una vez añadida la referencia creamos nuestra clase

  1. public class WatermarkTextBehavior : Behavior<TextBox>
  2.     {
  3.     

 

Ahora creamos una DependencyProperty donde vamos a asignar el texto que queremos mostrar en la marca de agua, todas las propiedades que queremos que el diseñador utilize a la hora de configurar el behavior seran DependencyProperty, en nuestro caso

  1. public class WatermarkTextBehavior : Behavior<TextBox>
  2.     {
  3.         public static readonly DependencyProperty TextProperty =
  4.      DependencyProperty.Register(«Text», typeof(string), typeof(WatermarkTextBehavior),
  5.                                         new FrameworkPropertyMetadata(string.Empty));
  6.         static readonly DependencyPropertyKey IsWatermarkedPropertyKey =
  7.             DependencyProperty.RegisterAttachedReadOnly(«IsWatermarked», typeof(bool), typeof(WatermarkTextBehavior),
  8.                                         new FrameworkPropertyMetadata(false));
  9.         public static readonly DependencyProperty IsWatermarkedProperty = IsWatermarkedPropertyKey.DependencyProperty;
  10.         public static bool GetIsWatermarked(TextBox tb)
  11.         {
  12.             return (bool)tb.GetValue(IsWatermarkedProperty);
  13.         }
  14.         public bool IsWatermarked
  15.         {
  16.             get { return GetIsWatermarked(AssociatedObject); }
  17.             private set { AssociatedObject.SetValue(IsWatermarkedPropertyKey, value); }
  18.         }
  19.         public string Text
  20.         {
  21.             get { return (string)base.GetValue(TextProperty); }
  22.             set { base.SetValue(TextProperty, value); }
  23.         }
  24.     

Una vez creadas las propiedades que necesitamos, debemos de sobrescribir los metodos OnAttached y OnDetaching estos métodos son lanzados cuando asignamos un behavior a un elemento y cuando lo quitamos. En nuestro caso lo que haremos sera subscribirnos a los eventos LostFocus y GotFocus, para ello los behavior tiene una propiedad que es AssociatedObject que contiene el objeto al cual se ha asignado el behavior.

 

  1. protected override void OnAttached()
  2.         {
  3.             base.OnAttached();
  4.             AssociatedObject.GotFocus += OnGotFocus;
  5.             AssociatedObject.LostFocus += OnLostFocus;
  6.             OnLostFocus(null, null);
  7.         }
  8.       
  9.         protected override void OnDetaching()
  10.         {
  11.             base.OnDetaching();
  12.             AssociatedObject.GotFocus -= OnGotFocus;
  13.             AssociatedObject.LostFocus -= OnLostFocus;
  14.         }

 

Ya solo queda implementar esta funcionalidad

  1. private void OnGotFocus(object sender, RoutedEventArgs e)
  2.         {
  3.             if (string.Compare(AssociatedObject.Text, this.Text, StringComparison.OrdinalIgnoreCase) == 0)
  4.             {
  5.                 AssociatedObject.Text = string.Empty;
  6.                 IsWatermarked = false;
  7.             }
  8.         }
  9.         private void OnLostFocus(object sender, RoutedEventArgs e)
  10.         {
  11.             if (string.IsNullOrEmpty(AssociatedObject.Text))
  12.             {
  13.                 AssociatedObject.Text = this.Text;
  14.                 IsWatermarked = true;
  15.             }
  16.         

Este seria nuestro primer behavior para utilizarlo ahora desde el Blend abrimos el proyecto y en Assets

 

image

Vemos la galería de behavior que vienen con Blend 3 además del que acabamos de crear, lo seleccionamos y lo arrastramos al objeto que queremos asignar, pero no a la ventana sino a la pestaña de Objects adnTimeline

image

 

Seleccionando el Behavior

image

 

Vemos que nos aparece en sus propiedades la que hemos configurado en nuestro caso el texto de la marca de agua

 

image

 

 

 

El Xaml nos quedaría

 

  1. <Window    xmlns=»http://schemas.microsoft.com/winfx/2006/xaml/presentation»    xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml»    xmlns:i=»clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity» xmlns:local=»clr-namespace:TestWpfApp»    x:Class=»TestWpfApp.Window3″    x:Name=»Window»    Title=»Window3″    Width=»640″ Height=»480″>    <Grid x:Name=»LayoutRoot»>        <TextBox x:Name=»txtText» Margin=»69,44,256,0″ VerticalAlignment=»Top» Height=»34″ TextWrapping=»Wrap»>            <i:Interaction.Behaviors>                <local:WatermarkTextBehavior Text=»Prueba»/>            </i:Interaction.Behaviors>        </TextBox>    </Grid>Window>
  2. </

 

Sencillo de crear y de utilizar.

Detectando Cuando estoy en Diseño

Un post rápido y sencillo, en Windows Forms para detectar cuando estábamos diseñando nuestro Form en código teníamos Windows Forms Control.DesignMode, en WPF tenemos su equivalente y es muy útil porque muchas veces Blend nos dará un error porque falla instrucciones en su constructor que en ejecución no fallan.

Yo utilizo una clase estática para ver si estamos en modo diseño o no

 

 

  1. public static class Designer
  2. {
  3.     private static DependencyObject dummy = new DependencyObject();
  4.     
  5.     public static bool InDesignMode
  6.     {
  7.         get { return DesignerProperties.GetIsInDesignMode(dummy); ;}
  8.     }

Como veis WPF tenemos la clase DesignerProperties que nos permite conocer si estamos en modo diseño, para utilizarla tan sencillo como

 

  1. if (!Designer.InDesignMode)
  2.                 {
  3.                     ….
  4.                 

 

Bien sencillo y muy útil

PriorityBinding o Binding en Cascada

En WPF existe el concepto PriorityBinding o se podía decir mejor binding en cascada, es decir podemos hacer un binding a diferentes propiedades indicándolas de mas deseable a menos deseable. Si la primera tiene el valor nulo o falla, coge el segundo binding, con la explicación que me cascado complicado de ver, lo mejor un ejemplo

  1.     <StackPanel>
  2.         <Image>
  3.             <Image.Source>
  4.                 <PriorityBinding>
  5.                     <Binding Path=»SlowImage» IsAsync=»True» />
  6.                     <Binding Path=»DefaultImage» />
  7.                 </PriorityBinding>
  8.             </Image.Source>
  9.         </Image>
  10.     </StackPanel>
  1.   public Window1()
  2.             {
  3.                 InitializeComponent();
  4.                 DefaultImage = new BitmapImage(new Uri(«http://slowimage/logo.png»));
  5.                 SlowImage = new BitmapImage(new Uri(«http:/defaultimage/logo.png»));
  6.                 this.DataContext = this;
  7.             }
  8.             private BitmapImage myDefaultImage;
  9.             public BitmapImage DefaultImage
  10.             {
  11.                 get { return this.myDefaultImage; }
  12.                 set
  13.                 {
  14.                     this.myDefaultImage = value;
  15.                     this.NotifyPropertyChanged(«Image»);
  16.                 }
  17.             }
  18.             private BitmapImage mySlowImage;
  19.             public BitmapImage SlowImage
  20.             {
  21.                 get
  22.                 {
  23.                     Thread.Sleep(5000);
  24.                     return this.mySlowImage;
  25.                 }
  26.                 set
  27.                 {
  28.                     this.mySlowImage = value;
  29.                     this.NotifyPropertyChanged(«SlowImage»);
  30.                 }
  31.             }
  32.             #region INotifyPropertyChanged Members
  33.             private void NotifyPropertyChanged(String info)
  34.             {
  35.                 if (PropertyChanged != null)
  36.                 {
  37.                     PropertyChanged(this, new PropertyChangedEventArgs(info));
  38.                 }
  39.             }
  40.             public event PropertyChangedEventHandler PropertyChanged;
  41.             #endregion
  42.         }

 

En este caso tenemos una imagen en la que indicamos que nuestro binding mas deseable es con la propiedad SlowImage  pero que si se produce un error o esta vacia le asigne el binding DefaultImage. Es importante que os fijéis en la propiedad IsAsync, es necesario que este a true, esto le dice a WPF que utilice otro thread para devolver esta propiedad. es importante para propiedades que tardan bastante en devolver un valor ya que lo importante es que cuando devuelva el valor aunque haya hecho el binding con la siguiente propiedad mostrara el valor de la propiedad que mas deseamos.

Para ver esto ultimo mirar este ejemplo

 

  1. <Window x:Class=»PriorityBindingTest.Window1″
  2.    xmlns=»http://schemas.microsoft.com/winfx/2006/xaml/presentation»
  3.    xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml»
  4.    Title=»Priority Binding Test» Height=»100″ Width=»200″
  5.    Name=»MainWindow»>
  6.     <Grid>
  7.     <TextBlock HorizontalAlignment=»Center» VerticalAlignment=»Center»>
  8.       <TextBlock.Text>
  9.         <PriorityBinding>
  10.           <Binding ElementName=»MainWindow» Path=»Slow» IsAsync=»True» />
  11.           <Binding ElementName=»MainWindow» Path=»Medium» IsAsync=»True» />
  12.           <Binding ElementName=»MainWindow» Path=»Fast» />
  13.         </PriorityBinding>
  14.       </TextBlock.Text>
  15.     </TextBlock>
  16.     </Grid>
  17. </Window>

 

  1. public partial class Window1 : Window
  2.   {
  3.     public Window1()
  4.     {
  5.       InitializeComponent();
  6.     }
  7.     public string Slow
  8.     {
  9.       get
  10.       {
  11.         Console.WriteLine(«LENTO: « +
  12.           Thread.CurrentThread.ManagedThreadId);
  13.         Thread.Sleep(2000);
  14.         return «ESTE ES EL MAS LENTO»;
  15.       }
  16.     }
  17.     public string Medium
  18.     {
  19.       get
  20.       {
  21.         Console.WriteLine(«MEDIO: « +
  22.           Thread.CurrentThread.ManagedThreadId);
  23.         Thread.Sleep(1000);
  24.         return «SOY EL DEL MEDIO»;
  25.       }
  26.     }
  27.     public string Fast
  28.     {
  29.       get
  30.       {
  31.         Console.WriteLine(«RAPIDO: « +
  32.           Thread.CurrentThread.ManagedThreadId);
  33.         return «SOY EL MAS RAPIDO»;
  34.       }
  35.     }
  36.   }

En el textbox ira mostrando cada uno de los valores primero el Fast, segundo el Medum y por ultimo el que deseamos Slow, si quitásemos IsAsync, no funcionaria y se quedaría con Fast.