views:

4923

answers:

4

Why is it that when I use a converter in my binding expression in WPF, the value is not updated when the data is updated.

I have a simple Person data model:

class Person : INotifyPropertyChanged
{
 public string FirstName { get; set; }
 public string LastName { get; set; }
}

My binding expression looks like this:

<TextBlock Text="{Binding Converter={StaticResource personNameConverter}" />

My converter looks like this:

class PersonNameConverter : IValueConverter
{
 public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
 {
  Person p = value as Person;
  return p.FirstName + " " + p.LastName;
 }

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

If I bind the data without a converter it works great:

<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />

What am I missing?

EDIT: Just to clarify a few things, both Joel and Alan are correct regarding the INotifyPropertyChanged interface that needs to be implemented. In reality I do actually implement it but it still doesn't work.

I can't use multiple TextBlock elements because I'm trying to bind the Window Title to the full name, and the Window Title does not take a template.

Finally, it is an option to add a compound property "FullName" and bind to it, but I'm still wondering why updating does not happen when the binding uses a converter. Even when I put a break point in the converter code, the debugger just doesn't get there when an update is done to the underlying data :-(

Thanks, Uri

+6  A: 
Joel B Fant
A: 

In Order for the binding to be updated, your person class needs to implement INotifyPropertyChanged to let the binding know that the object's properties have been udpated. You can also save yourself from the extra converter by providing a fullName property.

using System.ComponentModel;

namespace INotifyPropertyChangeSample
{
    public class Person : INotifyPropertyChanged
    {
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                if (firstName != value)
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                    OnPropertyChanged("FullName");
                }
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                if (lastName != value)
                {
                    lastName = value;
                    OnPropertyChanged("LastName");
                    OnPropertyChanged("FullName");
                }
            }
        }

        public string FullName
        {
            get { return firstName + " " + lastName; }
        } 

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #endregion
    }
}

Your Binding will now look like this:

<TextBlock Text="{Binding Person.FullName}" />
Alan Le
Adding a FullName property to the Person class solves the problem, but it's not the solution I'm looking for.
urini
A: 

I haven't check it but can you also try the following

<TextBlock Text="{Binding Path=/, Converter={StaticResource personNameConverter}}" />
rudigrobler
+8  A: 

You can also use a MultiBinding.. Bind to the Person object, the FirstName and LastName. That way, the value gets updated as soon as FirstName or LastName throws the property changed event.

<MultiBinding Converter="{IMultiValueConverter goes here..}">
    <Binding />
    <Binding Path="FirstName" />
    <Binding Path="LastName" />
</MultiBinding>

Or if you only use the FirstName and LastName, strip the Person object from the binding to something like this:

<MultiBinding Converter="{IMultiValueConverter goes here..}">
    <Binding Path="FirstName" />
    <Binding Path="LastName" />
</MultiBinding>

And the MultiValueConverter looks like this:

class PersonNameConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
            return values[0].ToString() + " " + values[1].ToString();
    }

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

But of course, the selected answer works as well, but a MultiBinding works more elegantly...

Arcturus
I agree. I'd completely forgotten about MultiBinding. And, after some thought, it really should be a ValueConverter's job (rather than Person) to format a full name because it receives a CultureInfo object.
Joel B Fant
Ahh thanks I have been trying to Hack around with IValueConverter to pass two arguments for a wile now... Didn't know about IMultiValueConverter.
Aaron