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
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…
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…
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“
);
}
}
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