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

4 comentarios sobre “Data Binding en WPF 1/2”

  1. Gracias Oscar. Desde luego te has salido en el aspecto pedagógico. Con material de esta calidad las cosas son bastante más simple. Es fácil encontrar manuales de referencias pero no un ejemplo tan claro y preciso como el que has expuesto.
    En resumen. Mil gracias

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *