Você está na página 1de 27

Breve introduccin a DataBinding en WPF

Data Binding
WPF
Introduccin

Oscar Alvarez Guerras
Pgina 1

Contenido
DataBinding ................................................................................................................................... 2
Validation Rules en WPF ............................................................................................................. 12
IValueConverter una Interfaz muy util en el DataBinding de WPF ............................................. 16
Haciendo Bindings de Enumeraciones en WPF ........................................................................... 18
MultiBinding en WPF ................................................................................................................... 23
Debugeando los Binding de WPF ................................................................................................ 25


Pgina 2

DataBinding
El propsito de la mayora 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 ms potentes de WPF, no
hay limitaciones en cuanto a lo que puedes hacer un Databinding.
Podramos realizar las tareas anteriores sin DataBinding pero con mucho mas cdigo, 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


Pgina 3

// 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 cdigo crea un objeto persona, y lo usa para inicializar los campos de la ventana. Cuando
se presiona el botn del cumpleaos, la persona se modifica, y los datos se muestran en un
message box
Es un ejemplo bastante sencillo y fcil de implementar, si ahora quisiramos 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 aplicacin se hace mas complicada, requiriendo cada vez
mas de estas lneas de cdigo

Pgina 4

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;
}
}


Pgina 5


Cuando la propiedad Age de la persona cambia, debida a la accin del handler de la
ventana, el objeto persona hace un raise del evento PropertyChanged Podramos
manualmente capturar este evento, colocar un handler y reaccionar ante el mismo, como
hacemos con cualquier otro handler
Notificacin 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");
}
}

Pgina 6

Con el cdigo anterior, ocurre una cascada de eventos que hacen que el handler del botn
del cumpleaos, no tenga que actualizar la UI
Grficamente, 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 tambin 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
botn Birthday, puede pasarnos esto



Pgina 7

El TextBox de Name tiene un valor diferente al que se muestra en el MessageBox, para
solucionar esto debemos agregar el siguiente cdigo
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
);
}
}



Pgina 8

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
buscbamos, tuvimos que escribir bastante cdigo.
El constructor de Window1 tuvo que inicializar los datos en los controles,
convirtiendo segn 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 segn sea necesario
El constructor de Window1 debe enganchar el handler de TextChanged pra propagar
cambios en la UI a la persona, convirtiendo
de String segn sea necesario
Claramente, la cantidad de cdigo a escribir se nos va de las manos, si la cantidad de
objetos, propiedades de los objetos y eventos aumenta. ems, este tipo de tareas parecen
bastante repetitivas. ah que WPF las abstrae en funcionalidades del framework, dndole el
nombre de Data Binding.
Hasta Ahora hemos visto lo que tenamos que hacer para realizar sincronizacin de los datos
con la interfaz de usuario sin Databinding, a partir de ahora 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 segn sea necesario


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
Pgina 9


En general, podemos pensar que Path es la ruta para acceder a una propiedad dentro del
objeto fuente de la informacin. 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 acta 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 instruccin especial. En WPF todo FrameworkElement y
todoFrameworkContentElement tienen una propiedad DataContext, y recordamos el
grafico del post donde veamos 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 cuestin
Esto es bastante til cuando queremos que dos componentes compartan el mismo data
source

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


Pgina 10


Hacer que ambos textbox compartan el mismo objeto Person, es cuestin 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 cdigo de sincronizacin se ha reducido
enormemente y sobre todo es mas escalable sin mucho impacto en el cdigo
El DataBinding tiene diferentes direcciones de enlace
Direccin 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 cambiaramos el valor y no se reflejara 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 tambin entre los propios objetos de interfaz de usuario, esto da un
gran poder en los diseos de interfaz de usuario.
Por ejemplo:
<Slider Name="Slider1" />
<TextBox Name="TextBox1" Text="{Binding ElementName=Slider1, Path=Value}" />
Pgina 11

En este cdigo cada vez que movamos el slider, se mostrar el valor en el TextBox
Cuando se hace el Databinding existe atributos opcionales para aadir 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 direccin y
frecuencia del binding
Converter para especificar una particular conversin de los datos a la interfaz de
usuario
ConverterParameter para pasar un parmetro 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


Pgina 12

Validation Rules en WPF
Siguiendo con el DataBinding de WPF vamos ha hablar de ValidationRules.Una regla de
validacin es una porcin de cdigo usada para validar un dato en el target, antes de que se
actualice el source, es decir se utiliza para que se realice la validacin en la Interfaz de
Usuario antes de que se modifique el source (por ejemplo una propiedad enlazada), pero
mejor vamos a verlo con ejemplos
El cdigo de validacin es realizado en una clase que extiende ValidationRule, haciendo un
override del mtodo Validate. Existe una regla de fabrica denominada
ExceptionValidationRule, que nos da la posibilidad de controlar la introduccin de datos
errneos como en este caso.
<Window ... xmlns:local="clr-namespace:WithBinding">
<Window.Resources>
...
<local:AgeToForegroundConverter x:Key="ageConverter" />
</Window.Resources>
...
<TextBox ...
Foreground="
{Binding
Path=Age,
Converter={StaticResource ageConverter}}">
<TextBox.Text>
<Binding Path="Age">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
...
En este caso en el Textbox de Edad hemos puesto la regla de validacion
ExceptioValidationRule nos comprobara que lo que metamos pueda ser transformado a int
porque la propiedad Age a la que esta enlazado es de tipo int y no puede hacer la
conversin.
Cuando un error de validacin ocurre, un objeto ValidationError es creado.Este contiene el
objeto usado para desplegar el mensaje de error, en general a nivel de la
UI. En el caso de la ExceptionValidationRule, el error de validacin esta en la propiedad
ErrorContent de este objeto que a su vez contiene la propiedad Message de la excepcin
capturada por esta regla. Para lograr acceder a esta informacin sobre los errores, podemos
escuchar por el evento ValidationError, attacheado al elemento de UI en cuestin que causa
el error.
// Window1.cs
...
public Window1( ) {
InitializeComponent( );
this.birthdayButton.Click += birthdayButton_Click;
Validation.AddErrorHandler(
this.ageTextBox,
ageTextBox_ValidationError);
}
void ageTextBox_ValidationError(
object sender,
ValidationErrorEventArgs e) {
MessageBox.Show((string)e.Error.ErrorContent,
"Validation Error");
}

Pgina 13

Debemos de decir que el mecanismo anterior funciona, solo si la propiedad
NotifyOnValidationError esta con valor True en el Binding del elemento en cuestin
<!-- Window1.xaml -->
...
<TextBox Name="ageTextBox" ...>
<TextBox.Text>
<Binding Path="Age"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
...
Al ejecutar esto con un error de validacin, obtenemos

Si bien lo anterior funciona bien, el mensaje no es muy amigable para el usuario final.
Podemos mejorarlo un poco customizando la manera de mostrar el error, como vemos en el
siguiente cdigo, en el que creamos una regla de validacin para comprobar el rango de
edad:

Pgina 14

public class NumberRangeRule : ValidationRule {
int min;
public int Min {
get { return min; }
set { min = value; }
}
int max;
public int Max {
get { return max; }
set { max = value; }
}
...
public override ValidationResult Validate(
object value,
System.Globalization.CultureInfo cultureInfo) {
int number;
if( !int.TryParse((string)value, out number) ) {
return new ValidationResult(
false,
"Invalid number format");
}
if( number < min || number > max ) {
return new ValidationResult(
false,
string.Format(
"Number out of range ({0}-{1})",
min, max));
}
return new ValidationResult(true, null);
}
}
Enganchamos la regla en el XAML como antes

<TextBox ...
Foreground="
{Binding Path=Age,
Converter={StaticResource ageConverter}}">
<TextBox.Text>
<Binding Path="Age">
<Binding.ValidationRules>
<local:NumberRangeRule Min="0"
Max="128" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Pgina 15


Podemos mejorar aun mas, colocando el mensaje de error en un tooltip sobre el elemento
que causo el error. Utilizando databinding sobre la propiedad ErrorContent podemos mostrar
el error:
<TextBox
Name="ageTextBox" ...
ToolTip=
"{Binding
ElementName=ageTextBox,
Path=(Validation.Errors)[0].ErrorContent}">
<TextBox.Text>
<Binding Path="Age">
<Binding.ValidationRules>
<local:NumberRangeRule Min="0" Max="128" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
En el ejemplo anterior, el tooltip esta asociado al primer elemento de la attached property
Errors. Si no hay errores, el tooltip esta vaco, cuando hay errores, se toma la informacin y
se despliega. Ya no necesitamos el NotifyOnValidationError en true, ya que el cambio en la
coleccin y el binding, mantiene los elementos sincronizados



Pgina 16

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 transformacin, con esta interfaz lo vamos a poder realizar.
La interfaz contiene dos mtodos:
Convert: es llamada cuando se hace la conversin 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 funcin de conversin
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 fijis 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 conversin que estamos realizando, pero no es exigido por WPF
Despus implementamos los mtodos 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 mtodo ConverBack porque el Binding es OneWay.

Pgina 17

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 parmetro
Converter relacionado con nuestro conversor.

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

Pgina 18

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 enumeracin
public enum Deporte
{
Futbol,
Baloncesto,
Atletismo,
Balonmano,
Golf
}
Donde indicamos el tipo de deporte que practicamos, aqu tenis 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");
}
}
}

Pgina 19

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 enumeracin por cdigo
PracticaComboBox.ItemsSource = Enum.GetValues(typeof(Deporte));
o usando solo XAML

<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}"
x:Key="sysEnum">
<ObjectDataProvider.MethodParameters>
Pgina 20

<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 quedara
<ComboBox ItemsSource="{Binding Source={StaticResource sysEnum}}"
SelectedValue="{Binding Practico}" Width="195" />
Si juntamos todo el Xaml de la ventana nos podra 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" />
Pgina 21

</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Practica: " />
<ComboBox ItemsSource="{Binding Source={StaticResource
sysEnum}}" SelectedValue="{Binding Practico}" Width="195" />
</StackPanel>
</StackPanel>

</Grid>
</Window>

El cs sera
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
Pgina 22




Pgina 23

MultiBinding en WPF
Vamos a fijarnos en el captulo anterior sobre como enlazar enumeraciones a un
ComboBox, si recordis ponamos el Nombre, primer Apellido y Segundo Apellido en la lista
de tres TextBox agrupados en un StackPanel
Esto no es lo mejor, lo bueno sera enlazar las tres propiedades a un solo TextBox, para ello
utilizaremos el MultiBinding de WPF.
La clase MultiBinding describe una coleccin 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 continuacin, aplicar la lgica 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 lgica
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 parmetro que acepta en este caso un
array de objects con los valores a enlazar
En este caso nos quedara
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();
}


}

Pgina 24

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 funcin Convert.

La aplicacin podis ver que quedara igual que el anterior




Pgina 25

Debugeando los Binding de WPF
Cuando desarrollamos en WPF, los binding son indispensables y a veces tenemos problemas
con ellos que no somos capaces de detectar y deseamos con todo el alma poder poner un
breakpoint y tener la posibilidad de debugear con todas las herramientas que nos da Visual
Studio.
Pero esto no es posible y para poder ver los errores de binding y poderlos analizar tenemos
diferentes alternativas
Output window
En la ventana Output de Visual Studio donde se displayan todos los mensajes, cuando se
produce un error de binding se muestra en esta ventana el error como por ejemplo
System.Windows.Data Error: 35 : BindingExpression path error: 'Password1' property not
found on 'object' ''LoginViewModel' (HashCode=28756230)'.
BindingExpression:Path=Password1; DataItem='LoginViewModel' (HashCode=28756230);
target element is 'TextBox' (Name='Password'); target property is 'Text' (type 'String')

Usar un converter
Si con el mensaje no conseguimos ver el problema una opcin es crear un converter y
cambiar el bindign al converter. En el converter si podemos poner un punto de ruptura y ver
el valor que entra al Binding

Crear una Dependency Property
Otra opcin es crear una DependencyProperty y poner un punto de ruptura

TraceLevel
PresentationTraceSources.TraceLevel es una clase que te permite enviar a la ventana de
Output mucha mas informacin del DataBinding, podemos monitorizar todos los pasos que
se producen en un Binding, para habilitarlo tenemos que aadir el namespace
System.Diagnostics y en el binding aadir PresentationTraceSources.TraceLevel=High. Esta
opcin aparece a partir de la versin 3.5

<Window x:Class="DebugBindings.Window1"... xmlns:trace="clr-
namespace:System.Diagnostics;assembly=WindowsBase">
<StackPanel>
<TextBox Text="{Binding ElementName=slider, Path=Value, Mode=OneWay,
trace:PresentationTraceSources.TraceLevel=High}" Height="30" Width="100"
Margin="10"/>
<Slider Minimum="0" Maximum="100" Value="20" Margin="10" x:Name="slider" />
StackPanel>
Window>
En la ventana de Output aparecer
Created BindingExpression (hash=17654054) for Binding (hash=44624228)
Path: 'Value'
BindingExpression (hash=17654054): Default update trigger resolved to LostFocus
BindingExpression (hash=17654054): Attach to System.Windows.Controls.TextBox.Text
(hash=52727599)
BindingExpression (hash=17654054): Resolving source
BindingExpression (hash=17654054): Found data context element: (OK)
Lookup name slider: queried TextBox (hash=52727599)
BindingExpression (hash=17654054): Resolve source deferred
Pgina 26

BindingExpression (hash=17654054): Resolving source
BindingExpression (hash=17654054): Found data context element: (OK)
Lookup name slider: queried TextBox (hash=52727599)
BindingExpression (hash=17654054): Activate with root item Slider (hash=54371668)
BindingExpression (hash=17654054): At level 0 - for Slider.Value found accessor
DependencyProperty(Value)
BindingExpression (hash=17654054): Replace item at level 0 with Slider (hash=54371668),
using accessor DependencyProperty(Value)
BindingExpression (hash=17654054): GetValue at level 0 from Slider (hash=54371668)
using DependencyProperty(Value): '20'
BindingExpression (hash=17654054): TransferValue - got raw value '20'
BindingExpression (hash=17654054): TransferValue - implicit converter produced '20'
BindingExpression (hash=17654054): TransferValue - using final value '20'
If I now enter text in the TextBox, I can check the Output Window to confirm that the
binding was indeed broken!
BindingExpression (hash=17654054): Deactivate
BindingExpression (hash=17654054): Replace item at level 0 with {NullDataItem}
BindingExpression (hash=17654054): Detach

Você também pode gostar