30 grandes post y tutoriales de Silverlight 3 y Blend 3

AdamKinney, nos ha puesto un post con los 30 mejores posts de Silverlight y Blend 3, aqui os dejo el post

 

Silverlight

Blend

Encoder

New Samples

Expression Branding

Although Expression Studio as whole has not been released (Blend 3 is currently available as an Release Candidate), the Expression site has been updated with a new look that matches the new branding.  Check out the new letter blocks below.

Expression Studio 3 logo Expression Blend 3 logo Expression Design 3 logo Expression Encoder 3 logoExpression Web 3 logo

IValueConverter una Interfaz muy util en el DataBinding de WPF

En los anteriores posts hemos visto como realizar el DataBinding en WPF, pero nos hemos centrado en los datos y algunas veces no nos valen el dato tal y como esta en la clase que enlazamos sino que requiere una transformación, con esta interfaz lo vamos a poder realizar.

La interfaz contiene dos métodos:

  • Convert: es llamada cuando se hace la conversión de datos desde la fuente hacia los componentes de UI (Int32 a Brush)
  • ConvertBack:ConvertBack es llamado cuando se convierte desde la UI a la fuente de datos

En cualquier caso, el valor actual y el tipo de datos son pasados a la función de conversión en el converter

Imaginemos que queremos que la propiedad ForeGroud de un TextBox se ponga de un color determinado en funcione de la edad, para ello utilizaremos un Converter

 

[ValueConversion(/*sourceType*/ typeof(int), 
                 /*targetType*/ typeof(Brush))]
public class AgeToForegroundConverter : IValueConverter {
   public object Convert(object value, Type targetType, ...) {
      if( targetType != typeof(Brush) ) { return null; }
         int age = int.Parse(value.ToString( ));
         return (age > 25 ? Brushes.Red : Brushes.Black);
      }
   }
   public object ConvertBack(object value, 
                             Type targetType, ...) {
         throw new NotImplementedException( );
   }
}

Si os fijáis la clase debe de tener el atributo ValueConversion donde se especifica el tipo del valor de entrada y el de salida en este caso int como entrda y brush como salida, es útil para documentar el tipo de conversión que estamos realizando, pero no es exigido por WPF

Después implementamos los métodos de Convert donde en este caso devolvemos una brocha roja o negra dependiendo si mayor o no de 25, en este caso no hace falta implementar el método ConverBack porque el Binding es OneWay.

El Binding en este caso seria

<!-- Window1.xaml -->
<Window ... xmlns:local="clr-namespace:WithBinding">
   <Window.Resources>
      <local:Person x:Key="Tom" ... />
      <local:AgeToForegroundConverter x:Key="ageConverter" />
   </Window.Resources>
   <Grid DataContext="{StaticResource Tom}">
      ...
      <TextBox
         Text="{Binding Path=Age}"
         Foreground="
            {Binding
               Path=Age,
               Converter={StaticResource ageConverter}}"
         ... />
      ...
      <Button ...
         Foreground="{Binding Path=Foreground, 
             ElementName=ageTextBox}">Birthday</Button>
   </Grid>
</Window>

Ponemos como recurso una referencia a la clase que implementa IValueConverter y en el Binding del ForeGround el Path esta enlazado a la propiedad Age pero tiene el parametro Converter relacionado con nuestro conversor.

 

De esta manera se implementa los converters, muy útiles en el DataBinding

 

 

Diferencias entre aplicaciones WPF cliente y XBAP

Muchas veces me preguntan las diferencias que hay entre el clásico ejecutable que generamos con WPF y que instalamos en el cliente y el concepto de XBAP que es una aplicación WPF también pero que es ejecutada a través del Navegador. Aunque los dos tipos de aplicaciones son hechas con WPF a la hora de diseñar la aplicación hay que tener muy claro si se va a elegir un modelo u otro.

Asi como el tipico ejecutable de WPF se instala como siempre en el cliente (xcopy, click-once) y con seguridad por defecto Full-trust que luego puede ser cambiado por el administrador del equipo, XBAP tiene una serie de restricciones que tenemos que tener en cuenta. La primera es que no se instala en el equipo cliente sino que se accede a través de un navegador que puede ser IE o FireFox, este acceso se realiza a través de una URL como si fuese una aplicación web.

una aplicación XBAP ya hemos dicho que no se instala en el equipo del cliente sino que se almacena en la cache y se ejecuta, si cambia la versión del servidor la copia de la cache se reemplaza por la nueva versión antes de ejecutarse, con lo que la instalación es automática y nos olvidamos de ella.

Las aplicaciones XBAP se ejecutan en una sandbox bajo partial trust, en otras palabras se permite el acceso a ciertas librerías de .NET pero a otras no que en WPF son accesibles , realmente los permisos son como una aplicación InternetZone. En la siguiente lista se indican que objetos están permitidos y cuales no

Permitidos

  • UI Controls
  • Text Input controls (incluido RichTextBox).
  • Flow documents y lectores
  • XPS documents
  • 2D drawing
  • 3D
  • Animation
  • Audio
  • Video
  • Pages
  • MessageBoxes
  • OpenFileDialog
  • Internal Drag and drop
  • llamadas a WCF services.
  • llamadas a ASMX services.

No Permitidos

  • Venatanas Stand-alone .
  • Dialogos Estandars.
  • Interop con controles windows o ActiveX.
  • Bitmap Effects
  • Shader Effects

Al estar en una InternetZone tienen ciertas operaciones restringidas, en el siguiente cuadro se muestra la lista de permisos de una aplicación XBAP

image

Como veis solo tiene activos 7 permisos, los demás permisos son denegados.

 

Así que no elijáis tan alegremente que es mejor una aplicación XBAP porque se puede acceder desde cualquier sitio

Crear el efecto de espejo en Expression Blend

Hola, me lo han preguntado varias veces como realizar ese “efecto chulo de reflejo en las imágenes”, no es nada difícil crearlo así que voy a explicarlo paso a paso.

1. Abrimos Expression Blend y creamos un nuevo proyecto WPF. Debajo de Objects and Timeline observais que tenemos un objeto Grid llamado LayoutRoot. lo seleccionamos y le cambiamos el color de fondo a negro

image

 

2. Vamos a Assets Library y seleccionamos el control StackPanel, lo arrastramos dentro del control LayoutRoot, y le damos el tamaño de Auto, Auto

image

 

3. Añadimos la imagen que queremos para hacer el reflejo, en la pestaña Projects haz click con el botón derecho del ratón en el nombre del proyecto y selecciona Add Existing Item… del menú, selecciona la imagen que quieras

 

Adding new image to project

4. Arrastra la imagen del proyecto y suéltala en el StackPanel, mientras tengas seleccionada la imagen vete a la pestaña Properties  y cambia el nombre a imgOriginalImage

 

image

5. Seleccionando la imagen haz click con el botón derecho y en el menu que aparece, selecciona Group Into y dentro de este selecciona Border, con esto creamos un border alrededor de la imagen

image

6.Seleccionamos el StackPanel y dibujamos otro Border pero no  como en el paso anterior, sino que vamos a Assets Library seleccionamos el Border ahora seleccionamos en Objects and Timeline el StackPanel y dibujamos el Border del tamaño de la imagen

image

7. Ahora necesitamos crear un VisualBrush , para ello lo vamos a crear en codigo XAML si pulsamos F11 cambiamos de vista en Blend y tenemos el codigo XAML

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="Reflections.MainWindow"
	x:Name="Window"
	Title="MainWindow"
	Width="640" Height="480">

	<Grid x:Name="LayoutRoot" Background="#FF000000">
		<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto" Height="Auto">
			<Border Margin="0,0,-924,0" Height="Auto">
				<Image x:Name=”imgOriginalImageHorizontalAlignment="Left" VerticalAlignment="Top" Width="173.07" Height="173.07" Source="encendido.jpg" Stretch="UniformToFill"/>
			</Border>
		</StackPanel>
		<Border HorizontalAlignment="Left" Margin="1,0,0,0" VerticalAlignment="Top" Width="172" Height="173.07" 
BorderBrush="#FF000000" BorderThickness="1,1,1,1" Background="{x:Null}"/>
	</Grid>
</Window>

 

Tenemos que modificar la linea del segundo control Border a

 

<Border HorizontalAlignment="Left" Margin="1,0,0,0" VerticalAlignment="Top" Width="172" Height="173.07" BorderBrush="#FF000000" BorderThickness="1,1,1,1" >
	<Border.Background>
		<VisualBrush Visual="{Binding ElementName=imgOriginalImage}"/>
	</Border.Background>
</Border>

Obtenemos la siguiente imagen

image

8.Ahora en el Border de abajo, vamos al panel Properties y en Transform localizamos la ppropiedad Scale  y en Y ponemos el valor a -1

 

image 

9.La imagen se ha dado la vuelta ahora para hacer el efecto fade-out para la imagen reflejada seleccionamos el Border y en Brushes  en la sección OpacityMask  con Gradient. Ponemos la transparencia del primer gradient stop a 0% y el segundo a 65%. El primer gradient stop  lo mueves un poco a la derecha

image

Nos quedaría:

 

image

 

Si queremos darle un efecto de pseudo 3D, seleccionamos el Border de reflejo y jugando con la propiedad Skew podemos obtener los siguiente

 

image

 

 

MultiBinding en WPF

Vamos a fijarnos en  el post anterior sobre como enlazar enumeraciones a un ComboBox, si recordáis poníamos el Nombre, primer Apellido y Segundo Apellido en la lista de tres TextBox agrupados en un StackPanel

 

 

Esto no es lo mejor, lo bueno sería enlazar las tres propiedades a un solo TextBox, para ello utilizaremos el MultiBinding de WPF.

La clase MultiBinding describe una colección de objetos Binding asociados a una sola propiedad de destino de enlace.
MultiBinding permite enlazar una propiedad del destino de enlace a una lista de propiedades de origen y, a continuación, aplicar la lógica para generar un valor con las entradas indicadas. En su momento ya vimos el post de Converter de WPF, ahora necesitamos la interfaz IMultiValueConverter , que proporciona una forma de aplicar la lógica personalizada a una clase MultiBinding, esta interfaz tiene los mismos metodos que la interfaz IValueConverter: Convert y ConvertBack, solo que en este caso si nos fijamos en las firmas

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)

Podemos observar que la diferencia esta en el primer parametro que acepta en este caso un array de objects con los valores a enlazar

En este caso nos quedaria

public class NombreConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            //Convertimos a Nombre Apellido 1 Apellido2
            return values[0].ToString() + " " + values[1].ToString() + " " + values[2].ToString();
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }


    }

El Xaml en este caso seria

 

<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" Grid.Column="0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="0,0,0,10">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock  Margin="0,0,5,0" >
                                <TextBlock.Text>
                                    <MultiBinding Converter="{StaticResource Convertidor}">
                                         <Binding Path="Nombre"/>
                                         <Binding Path="Apellido1"/>
                                         <Binding Path="Apellido2"/>
                                    </MultiBinding>
                                 </TextBlock.Text>
                            </TextBlock>
                        </StackPanel>
                        <TextBlock Text="{Binding Practico}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Es importante ver que el orden que pongamos en el Xaml será el orden que tenga en el array values de la función Convert.

 

La aplicación podéis ver que quedaría igual que el anterior

 

image

 

Haciendo Bindings de Enumeraciones en WPF

Una pregunta muy recurrente cuando explico el Binding es como hacer Binding de Enums a un ComboBox, en este post voy a explicarlo mediante un ejemplo, imaginemos que tenemos la enumeración

 public enum Deporte
    {
        Futbol,
        Baloncesto,
        Atletismo,
        Balonmano,
        Golf
    }

Donde indicamos el tipo de deporte que practicamos, aquí tenéis la clase Persona

public class Persona : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private string _nombre;
        public string Nombre
        {
            get
            {
                return _nombre;
            }
            set
            {
                if (value != _nombre)
                {
                    _nombre = value;
                    NotifyPropertyChanged("Nombre");
                }
            }
        }

        private string _apellido1;
        public string Apellido1
        {
            get
            {
                return _apellido1;
            }
            set
            {
                if (value != _apellido1)
                {
                    _apellido1 = value;
                    NotifyPropertyChanged("Apellido1");
                }
            }
        }

        private string _apellido2;
        public string Apellido2
        {
            get
            {
                return _apellido2;
            }
            set
            {
                if (value != _apellido2)
                {
                    _apellido2 = value;
                    NotifyPropertyChanged("Apellido2");
                }
            }
        }

        private Deporte _practico;
        public Deporte Practico
        {
            get
            {
                return _practico;
            }
            set
            {
                if (value != _practico)
                {
                    _practico = value;
                    NotifyPropertyChanged("Practico");
                }
            }
        }

    }

Ahora creamos la carga de una ObservableCollection<Persona> al DataContext

void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = new ObservableCollection<Persona>()
                              {
                                  new Persona("Oscar", "Alvarez", "Guerras", Deporte.Atletismo),
                                  new Persona("Jose", "Rodriguez", "Fernandez", Deporte.Futbol),
                                  new Persona("Jorge", "Elorza", "Blanco", Deporte.Baloncesto),
                                  new Persona("Noelia", "Gomez", "Souto", Deporte.Futbol)
                              };
        }

Ahora tenemos dos maneras de enlazar la enumeración por código

PracticaComboBox.ItemsSource = Enum.GetValues(typeof(Deporte));

o usando solo XAML

 

<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="sysEnum">
 	<ObjectDataProvider.MethodParameters> 
	    <x:Type TypeName="local:Deporte" />
         	</ObjectDataProvider.MethodParameters> 
</ObjectDataProvider> 

Creamos on ObjectDataProvider para el tipo Deporte con el nombre sysEnum, demanera que para enlazarlo con el combo nos quedaría

<ComboBox ItemsSource="{Binding Source={StaticResource sysEnum}}" SelectedValue="{Binding Practico}"  Width="195" />

Si juntamos todo el Xaml de la ventana nos podría quedar

 

<Window x:Class="WPFtestEnumBindings.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFtestEnumBindings"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    Title="Window1" Height="500" Width="500">
    <Window.Resources>
        <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type System:Enum}" x:Key="sysEnum">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:Deporte" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>

    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="132*" />
            <ColumnDefinition Width="146*" />
        </Grid.ColumnDefinitions>
        <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" Grid.Column="0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="0,0,0,10">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Nombre}" Margin="0,0,5,0" />
                            <TextBlock Text="{Binding Apellido1}" Margin="0,0,5,0"/>
                            <TextBlock Text="{Binding Apellido2}" />
                        </StackPanel>
                        <TextBlock Text="{Binding Practico}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <StackPanel Grid.Column="1">
            <StackPanel Orientation="Horizontal">
                <Label Content="Nombre: " />
                <TextBox Text="{Binding Nombre}"  Width="186" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label Content="Apellido 1: " />
                <TextBox Text="{Binding Apellido1}"  Width="177" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label Content="Apellido 2: " />
                <TextBox Text="{Binding Apellido2}" Width="175" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label Content="Practica: " />
                <ComboBox ItemsSource="{Binding Source={StaticResource sysEnum}}" SelectedValue="{Binding Practico}"  Width="195" />
            </StackPanel>
        </StackPanel>

    </Grid>
</Window>

 

El cs sería

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFtestEnumBindings
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(Window1_Loaded);
        }

        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = new ObservableCollection<Persona>()
                              {
                                  new Persona("Oscar", "Alvarez", "Guerras", Deporte.Atletismo),
                                  new Persona("Jose", "Rodriguez", "Fernandez", Deporte.Futbol),
                                  new Persona("Jorge", "Elorza", "Blanco", Deporte.Baloncesto),
                                  new Persona("Noelia", "Gomez", "Souto", Deporte.Futbol)
                              };
        }
    }
}

 

Y nos da como resultado la ventana

 

image

 

Probarlo y a disfrutar…

 

 

 

Primer Libro de SketchFlow

La tecnología salió este viernes y ya tenemos el primer libro Dynamic Prototyping with SketchFlow in Expression Blend, todavía no esta escrito pero tenemos a nuestra disposición y gratis el primer capitulo de 100 paginas muy completo para iniciarse en la creación de prototipos.

Yo por mi parte ando jugueteando con SketchFlow e iré publicando posts sobre SkectFlow basándome en este libro que de un rápido vistazo me ha parecido interesante.

A prototipar…

 

image

DataBinding en WPF 2/2

En el anterior post vimos que teníamos que hacer para realizar sincronización de los datos con la interfaz de usuario sin Databinding, en este vamos a ver el DataBinding

Consiste en registrar un par de propiedades, una de ellas en un objeto (Person.Name), la otra en la interfaz grafica (Textbox.Text), con el motor de Data Binding
Este motor es el que se encarga de mantener ambas sincronizadas, convirtiendo los tipos de datos según sea necesario

 

image

Para registrar ambas propiedades (tanto del elemento, que seria la clase persona como del objeto que seria el Textbox), utilizamos el objeto Binding

Todas las siguientes son equivalentes…

 

<TextBox ...>
   <TextBox.Text>
      <Binding Path="Age" />
   </TextBox.Text>
</TextBox>
<TextBox Text="{Binding Path=Age}" />
<TextBox Text="{Binding Age}" />

es equivalente a…

image

En general, podemos pensar que Path es la ruta para acceder a una propiedad dentro del objeto fuente de la información. En el caso anterior, el binding se da entre el
TextBox (propiedad Text) y la propiedad Age de un objeto que será determinado luego.

En el caso anterior, el textbox actúa como binding target , consumiendo los cambios binding source. El binding target puede ser cualquier elemento WPF, pero solo podemos hacer binding contra las dependency properties. El binding source puede ser cualquier propiedad de cualquier elemento WPF.En este caso, el binding source puede especificarse en runtime
Sin embargo, en general, el binding provendrá de un Data Context.

Un DataContext es el lugar en el que los bindings buscan por su data source si no se les ha dado ninguna instrucción especial. En WPF todo FrameworkElement y todo FrameworkContentElement tienen una propiedad DataContext, y recordamos el grafico del post donde veíamos que todos los controles derivan de FrameWorkElement

El DataContext es de tipo Object, por lo que podemos colocar cualquier cosa que deseemos.

Cuando se busca un objeto para utilizar como data source de un binding, se recorre el árbol de componentes en forma inversa, partiendo del componente en cuestión
Esto es bastante útil cuando queremos que dos componentes compartan el mismo data source

image

Esto funciona así…

  • El binding busca un DataContext en el propio TextBox
  • El binding busca un DataContext en la Grid
  • El binding busca un DataContext en el Window

 

Hacer que ambos textbox compartan el mismo objeto Person, es cuestión de colocarlo en el DataContext del Grid

// Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
namespace WithBinding {
   public partial class Window1 : Window {
      Person person = new Person("Tom", 11);
      public Window1( ) {
         InitializeComponent( );
         grid.DataContext = person;
         this.birthdayButton.Click += birthdayButton_Click;
      }
      void birthdayButton_Click(object sender, 
                                RoutedEventArgs e) {
         ++person.Age;
         MessageBox.Show(string.Format(
                  "Happy Birthday, {0}, age {1}!",
                   person.Name,
                   person.Age),
                   "Birthday");
      }
   }
}

Entonces, si bien la funcionalidad es la misma, el código de sincronización se ha reducido enormemente y sobre todo es mas escalable sin mucho impacto en el código

El DataBinding tiene diferentes direcciones de enlace

Dirección del Databind

  • OneWay: de origen a destino y cuando el origen cambie
  • TwoWat: en ambos sentidos y con notificaciones de cambio
  • OneWayToSource: es igual que OneWay pero al reves, de destino a origen
  • OneTime: enlace a datos igual que OneWay pero únicamente una vez.

Debemos de tener en cuenta que todas las clases que hagamos Binding deben de ser a DependencyProperties o Propiedad, si son a estas ultimas las clases deben de implementar el interfaz INotifyPropertyChanged para notificar los cambios de la clase al Interfaz de Usuario, sino cambiaríamos el valor  y no se reflejaría en el control enlazado.

Recordar que en el DataBinding no solo lo podemos hacer de clases de negocio a objetos de interfaz de usuario, sino también entre los propios objetos de interfaz de usuario, esto da un gran poder en los diseños de interfaz de usuario.

Por ejemplo:

<Slider Name="Slider1" />
<TextBox Name="TextBox1" Text="{Binding ElementName=Slider1, Path=Value}" />

En este código cada vez que movamos el slider, se mostrar el valor en el TextBox

Cuando se hace el Databinding existe atributos opcionales para añadir a las expresiones de Binding

 

  • Source – usado en lugar  ElementName para referirse al objeto
  • RelativeSource –usado en lugar ElementName para referirse al elemento actual. con los valores Self, FindAncestor, y PreviousData.
  • Mode – OneTime|OneWay|TwoWay|OneWayToSource – para indicar la dirección y frecuencia del binding
  • Converter – para especificar una particular conversión de los datos a la interfaz de usuario
  • ConverterParameter – para pasar un parámetro adicional al converter
  • UpdateSourceTrigger – LostFocus|PropertyChanged|Explicit – para indicar cuando modificar la propiedad destino
  • FallbackValue – para indicar un valor por defecto cuando el binding falla

Data Binding en WPF 1/2

El propósito de la mayoría de las aplicaciones que hacemos es presentar datos, y permitir que los usuarios los modifiquen teniendo que realizar las operaciones de: ordenar, filtrar, convertir, relacionarlos…

WPF incorpora un motor de Databinding que es una de las partes más potentes de WPF, no hay limitaciones en cuanto a lo que puedes hacer un Databinding.

Podríamos realizar las tareas anteriores sin DataBinding pero con mucho mas código, por ejemplo para la ventana

 

image

El XAML seria

<!-- Window1.xaml -->
<Window ...>
   <Grid>
      ...
      <TextBlock ...>Name:</TextBlock>
      <TextBox Name="nameTextBox" ... />
      <TextBlock ...>Age:</TextBlock>
      <TextBox Name="ageTextBox" ... />
      <Button Name="birthdayButton" ...>Birthday</Button>
   </Grid>
</Window>

El codigo de la clase Person

 

public class Person {
   string name;
   public string Name {
      get { return this.name; }
      set { this.name = value; }
   }
   int age;
   public int Age {
      get { return this.age; }
      set { this.age = value; }
   }
   public Person( ) {}
   public Person(string name, int age) {
      this.name = name;
      this.age = age;
   }
}

En el code-behind de la ventana

 

// Window1.xaml.cs
...
public class Person {...}
public partial class Window1 : Window {
   Person person = new Person("Tom", 11);
   public Window1( ) {
      InitializeComponent( );
      // Fill initial person fields
      this.nameTextBox.Text = person.Name;
      this.ageTextBox.Text = person.Age.ToString();
      // Handle the birthday button click event
      this.birthdayButton.Click += birthdayButton_Click;
   }
   void birthdayButton_Click(object sender, RoutedEventArgs e) {
      ++person.Age;
      MessageBox.Show(string.Format(
                          "Happy Birthday, {0}, age {1}!",
                          person.Name,
                          person.Age),
                       "Birthday");
   }
}

El código crea un objeto persona, y lo usa para inicializar los campos de la ventana. Cuando se presiona el botón del cumpleaños, la persona se modifica, y los datos se muestran en un message box

Es un ejemplo bastante sencillo y fácil de implementar, si ahora quisiéramos reflejar los cambios en la ventana principal

 

void birthdayButton_Click(object sender,  
                          RoutedEventArgs e) {
   ++person.Age;
   // Manually update the UI
   this.ageTextBox.Text = person.Age.ToString();
   MessageBox.Show(string.Format(
                       "Happy Birthday, {0}, age {1}!",
                       person.Name,
                       person.Age),
                   "Birthday");
}

Parece una forma sencilla de mejorarla, pero tiene un problema No escala bien a medida que la aplicación se hace mas complicada, requiriendo cada vez
mas de estas líneas de código

Cambios en los objetos

Una forma mas robusta para que la UI lleve registro de los cambios, es que el objeto avise cuando una propiedad cambia, por ejemplo, lanzando un evento La forma correcta de hacer esto, es que el objeto implemente INotifyPropertyChanged

using System.ComponentModel; // INotifyPropertyChanged
...
public class Person : INotifyPropertyChanged {
   // INotifyPropertyChanged Members
   public event PropertyChangedEventHandler 
                                     PropertyChanged;
   protected void Notify(string propName) {
      if( this.PropertyChanged != null ) {
         PropertyChanged(
               this, 
               new PropertyChangedEventArgs(propName));
      }
   }
   string name;
   public string Name {
      get { return this.name; }
      set {
          if( this.name == value ) { return; }
          this.name = value;
          Notify("Name");
      }
   }
   int age;
   public int Age {
      get { return this.age; }
      set {
          if(this.age == value ) { return; }
          this.age = value;
          Notify("Age");
      }
   }
   public Person( ) {}
   public Person(string name, int age) {
      this.name = name;
      this.age = age;
   }
}

 

Cuando la propiedad Age de la persona cambia, debida a la acción del handler de la ventana,  el objeto persona hace un raise del evento PropertyChanged Podríamos manualmente capturar este evento, colocar un handler y reaccionar ante el mismo, como hacemos con cualquier otro handler…

Notificación de cambios // Window1.xaml.cs
...
public class Person : INotifyPropertyChanged {...}
public partial class Window1 : Window {
   Person person = new Person("Tom", 11);
   public Window1( ) {
      InitializeComponent( );
      // Fill initial person fields
      this.nameTextBox.Text = person.Name;
      this.ageTextBox.Text = person.Age.ToString( );
      
      // Watch for changes in Tom's properties
      person.PropertyChanged += person_PropertyCha
      // Handle the birthday button click event
      this.birthdayButton.Click += birthdayButton_Click;
}
void person_PropertyChanged(
               object sender,
               PropertyChangedEventArgs e) {
   switch( e.PropertyName ) {
      case "Name":
          this.nameTextBox.Text = person.Name;
          break;
      case "Age":
          this.ageTextBox.Text = person.Age.ToStrin
          break;
   } 
}
   void birthdayButton_Click(object sender, 
                             RoutedEventArgs e) {
      ++person.Age; 
      // person_PropertyChanged will update ageTextBox
      MessageBox.Show(
                string.Format(
                      "Happy Birthday, {0}, age {1}!",
                       person.Name,
                       person.Age),
                "Birthday");
   }
}

Con el código anterior, ocurre una cascada de eventos que hacen que el handler del botón del cumpleaños, no tenga que actualizar la UI
Gráficamente, se puede apreciar en el siguiente diagrama este flujo de eventos…

image

Sin embargo, hemos resuelto parte del problema Aun tenemos que actualizar manualmente en el handler del cambio de propiedad, la interfaz grafica

Pero tenemos otro problema…

Necesitamos también un mecanismo que detecte los cambios en la interfaz grafica, y los propague hacia el objeto. En el ejemplo anterior, si cambiamos el nombre y presionamos el botón Birthday, puede pasarnos esto…

 

image

El TextBox de Name tien un valor diferente al que se muestra en el MessageBox, para solucionar esto debmos agregar el siguiente código

public partial class Window1 : Window {
   Person person = new Person("Tom", 11);
   public Window1( ) {
      InitializeComponent( );
      // Fill initial person fields
      this.nameTextBox.Text = person.Name;
      this.ageTextBox.Text = person.Age.ToString( );
      // Watch for changes in Tom's properties
      person.PropertyChanged += person_PropertyChanged;
      // Watch for changes in the controls
      this.nameTextBox.TextChanged += nameTextBox_TextChanged;
      this.ageTextBox.TextChanged += ageTextBox_TextChanged;
      // Handle the birthday button click event
      this.birthdayButton.Click += birthdayButton_Click;
   }
  
void nameTextBox_TextChanged(object sender, TextChangedEventArgs e) {
      person.Name = nameTextBox.Text;
   }
   void ageTextBox_TextChanged(object sender, TextChangedEventArgs e) {
      int age = 0;
      if( int.TryParse(ageTextBox.Text, out age) ) {
         person.Age = age;
      }
   }
   void birthdayButton_Click(object sender, RoutedEventArgs e) {
       ++person.Age;
       MessageBox.Show(string.Format(
                            "Happy Birthday, {0}, age {1}!",
                            person.Name,
                            person.Age),
                            "Birthday“
                        );
   }
}

 

image

Sin importar entonces en donde se de el cambio (objeto ,UI), ambos elementos se mantienen sincronizados.in embargo, a pesar de que tenemos lo que
buscábamos, tuvimos que escribir bastante código.

  • El constructor de Window1 tuvo que inicializar los datos en los controles, convirtiendo según necesidad a String
    El constructor Window1 tuvo que enganchar los handlers de los eventos PropertyChanged del objeto Person, para tomar los
    nuevos datos de la persona, convirtiendo a String según sea necesario
  • El constructor de Window1 debe enganchar el handler de TextChanged pra propagar cambios en la UI a la persona, convirtiendo
    de String según sea necesario

Claramente, la cantidad de código a escribir se nos va de las manos, si la cantidad de objetos, propiedades de los objetos y eventos aumenta. emás, este tipo de tareas parecen
bastante repetitivas. ahí que WPF las abstrae en funcionalidades del framework, dándole el nombre de Data Binding