Связывание данных WPF и IValueConverter
Почему это происходит, когда я использую конвертер в моем выражении привязки в 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
Ответы
Ответ 1
(см. правки ниже: последние: # 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
DataContext
.)
Title="{Binding Path=FullName, Mode=OneWay}"
Если вы редактируете имена в TextBox
и хотите, чтобы имя изменилось сразу же, а не когда тег TextBox
потерял фокус, вы можете сделать это:
<TextBox Name="FirstNameEdit"
Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />
Я знаю, что вы не хотели использовать свойство FullName
, но все, что могло бы сделать то, что вы хотите, вероятно, было бы частью устройства Rube Goldberg. Например, реализовать свойство INotifyPropertyChanged
и a 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>
Ответ 2
Вы также можете использовать 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 работает более элегантно...
Ответ 3
В порядке обновления привязки ваш класс человека должен реализовать 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}" />
Ответ 4
Я не проверял его, но вы также можете попробовать следующее
<TextBlock Text="{Binding Path=/, Converter={StaticResource personNameConverter}}" />