WPF 数据绑定和 IValueConverter

发布于 2024-07-06 23:09:59 字数 1266 浏览 6 评论 0原文

为什么当我在 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}" />

我缺少什么?

编辑: 只是为了澄清一些事情,Joel 和 Alan 对于需要实现的 INotifyPropertyChanged 接口都是正确的。 实际上我确实实现了它,但它仍然不起作用。

我无法使用多个 TextBlock 元素,因为我试图将窗口标题绑定到全名,并且窗口标题不采用模板。

最后,可以选择添加复合属性“FullName”并绑定到它,但我仍然想知道为什么当绑定使用转换器时不会发生更新。 即使我在转换器代码中放置一个断点,当对底层数据进行更新时,调试器也无法到达那里:-(

谢谢, 乌里

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

梦行七里 2024-07-13 23:09:59

您还可以使用 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 工作得更优雅......

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

夏日落 2024-07-13 23:09:59

(请参阅下面的编辑;最新:#2)

它不会更新,因为您的 Person 对象无法通知任何 FirstNameLastName 已更改。 查看此问题

以下是实现 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 和这样工作得很好,我认为你根本不需要转换器。 多个 TextBlock 同样有效,并且在本地化为从右到左的语言时实际上可以更好地工作。

编辑2

我已经弄清楚了。 它不会被通知属性已更新,因为它绑定到对象本身,而不是这些属性之一。 即使当我将 Person 设置为 DependencyObject 并设置 FirstNameLastName DependencyProperties 时,它不会更新。

必须使用FullName 属性,我已经更新了上面的Person 类的代码来反映这一点。 然后就可以绑定Title了。 :我已将 Person 对象设置为 WindowDataContext。)

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

注意 编辑 TextBox 中的名称并希望名称更改立即反映出来,而不是在 TextBox 失去焦点时,您可以这样做:

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

我知道您不想使用FullName 属性,但是任何能够实现您想要的功能的东西都可能有点像 Rube Goldberg 设备。 例如在 Window 类本身上实现 INotifyPropertyChangedPerson 属性,让 Window 监听 >PropertyChanged 事件以触发 WindowPropertyChanged 事件,并使用如下所示的相对绑定。 您还可以在 InitializeComponent() 之前设置 Person 属性,或者在设置 Person 属性之后触发 PropertyChanged,以便当然,它会出现。 (否则在 InitializeComponent() 期间它将为 null,并且需要知道它何时是 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>

(see edits below; latest: #2)

It isn't updating because your Person object is not capable of notifying anything that the value of FirstName or LastName has changed. See this Question.

And here's how you implement INotifyPropertyChanged. (Updated, see Edit 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 ) );
            }
        }
    }
}

Edit 1

Actually, since you're after the first name and last name updating, and Path=FirstName and such works just fine, I don't think you'll need the converter at all. Multiple TextBlocks are just as valid, and can actually work better when you're localizing to a right-to-left language.

Edit 2

I've figured it out. It's not being notified that the properties have updated because it is binding to the object itself, not one of those properties. Even when I made Person a DependencyObject and made FirstName and LastName DependencyProperties, it wouldn't update.

You will have to use a FullName property, and I've update the code of the Person class above to reflect that. Then you can bind the Title. (Note: I've set the Person object as the Window's DataContext.)

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

If you're editing the names in a TextBox and want the name changed reflected immediately instead of when the TextBox loses focus, you can do this:

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

I know you didn't want to use a FullName property, but anything that would accomplish what you want would probably be a bit of a Rube Goldberg device. Such as implementing INotifyPropertyChanged and a Person property on the Window class itself, having the Window listen on the PropertyChanged event in order to fire the Window's PropertyChanged event, and using a relative binding like the following. You'd also have set the Person property before InitializeComponent() or fire PropertyChanged after setting the Person property so that it shows up, of course. (Otherwise it will be null during InitializeComponent() and needs to know when it's a 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>
为人所爱 2024-07-13 23:09:59

为了更新绑定,您的 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}" />

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}" />
南冥有猫 2024-07-13 23:09:59

我还没有检查过,但你也可以尝试以下方法吗

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

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

<TextBlock Text="{Binding Path=/, Converter={StaticResource personNameConverter}}" />
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文