Связывание данных WPF и IValueConverter - PullRequest
13 голосов
/ 06 октября 2008

Почему при использовании в своем выражении связывания в WPF конвертера значение не обновляется при обновлении данных.

У меня есть простая модель данных Person:

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

Мое связующее выражение выглядит так:

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

Мой конвертер выглядит так:

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

Если я связываю данные без конвертера, он прекрасно работает:

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

Что мне не хватает?

EDIT: Просто чтобы прояснить некоторые вещи, Джоэл и Алан правы относительно интерфейса INotifyPropertyChanged, который должен быть реализован. На самом деле я действительно реализую его, но он все еще не работает.

Я не могу использовать несколько элементов TextBlock, потому что я пытаюсь привязать заголовок окна к полному имени, а заголовок окна не принимает шаблон.

Наконец, есть возможность добавить составное свойство "FullName" и связать его, но мне все еще интересно, почему обновление не происходит, когда привязка использует конвертер. Даже когда я ставлю точку останова в коде конвертера, отладчик просто не попадает туда, когда выполняется обновление базовых данных: - (

Спасибо, Uri

Ответы [ 4 ]

14 голосов
/ 07 октября 2008

Вы также можете использовать MultiBinding. Привязка к объекту Person, FirstName и LastName. Таким образом, значение обновляется, как только FirstName или LastName генерируют событие измененного свойства.

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

Или, если вы используете только FirstName и LastName, удалите объект Person из привязки примерно так:

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

И MultiValueConverter выглядит так:

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

Но, конечно, выбранный ответ также работает, но MultiBinding работает более элегантно ...

11 голосов
/ 06 октября 2008

(см. Правки ниже; последний: # 2)

Он не обновляется, потому что ваш объект Person не может ничего уведомлять об изменении значения FirstName или LastName. См. Этот вопрос .

А вот как вы реализуете INotifyPropertyChanged. ( Обновлено, см. Редактировать 2 )

using System.ComponentModel;

class Person : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    string _firstname;
    public string FirstName {
        get {
            return _firstname;
        }
        set {
            _firstname = value;
            onPropertyChanged( "FirstName", "FullName" );
        }
    }

    string _lastname;
    public string LastName {
        get {
            return _lastname;
        }
        set {
            _lastname = value;
            onPropertyChanged( "LastName", "FullName" );
        }
    }

    public string FullName {
        get {
            return _firstname + " " + _lastname;
        }
    }

    void onPropertyChanged( params string[] propertyNames ) {
        PropertyChangedEventHandler handler = PropertyChanged;

        if ( handler != null ) {
            foreach ( var pn in propertyNames ) {
                handler( this, new PropertyChangedEventArgs( pn ) );
            }
        }
    }
}

Редактировать 1

На самом деле, поскольку вы обновляете имя и фамилию, и Path=FirstName, и все это прекрасно работает, я не думаю, что вам вообще понадобится конвертер. Несколько TextBlocks также действительны и могут работать лучше, когда вы переводите на язык с написанием справа налево.

Редактировать 2

Я понял это. Не сообщается, что свойства обновлены, потому что это привязка к самому объекту, а не к одному из этих свойств. Даже когда я сделал Person a DependencyObject и сделал FirstName и LastName DependencyProperties, он не обновился бы.

Вы должны будете использовать свойство FullName, и я обновил код класса Person выше, чтобы отразить это. Тогда вы можете связать Title. ( Примечание: Я установил объект Person как Window s DataContext.)

Title="{Binding Path=FullName, Mode=OneWay}"

Если вы редактируете имена в TextBox и хотите, чтобы имя изменилось немедленно, вместо того, чтобы TextBox потерял фокус, вы можете сделать это:

<TextBox Name="FirstNameEdit"
    Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />

Я знаю, что вы не хотели использовать свойство FullName, но все, что могло бы выполнить то, что вы хотите, вероятно, было бы чем-то вроде устройства Rube Goldberg. Например, реализация INotifyPropertyChanged и свойства Person в самом классе Window, прослушивание Window события PropertyChanged для запуска события Window PropertyChanged и использование Относительная привязка вроде следующего. Вы также установили бы свойство Person до InitializeComponent() или уволили PropertyChanged после установки свойства Person, чтобы оно, конечно, отображалось. (В противном случае он будет null во время InitializeComponent() и должен знать, когда это Person.)

<Window.Resources>
    <loc:PersonNameConverter
        x:Key="conv" />
</Window.Resources>
<Window.Title>
    <Binding
        RelativeSource="{RelativeSource Self}"
        Converter="{StaticResource conv}"
        Path="Person"
        Mode="OneWay" />
</Window.Title>
1 голос
/ 06 октября 2008

Для того чтобы привязка была обновлена, ваш класс person должен реализовать INotifyPropertyChanged, чтобы дать привязке знать, что свойства объекта были обновлены. Вы также можете спасти себя от дополнительного конвертера, указав свойство fullName.

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

Ваша привязка теперь будет выглядеть так:

<TextBlock Text="{Binding Person.FullName}" />
0 голосов
/ 07 октября 2008

Я не проверял, но вы также можете попробовать следующее

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