绑定对于嵌套属性无声地失败

时间:2013-12-17 09:05:17

标签: wpf binding .net-4.0 inotifypropertychanged targetnullvalue

我正在尝试将依赖项属性绑定到具有多级属性路径的INotifyPropertyChanged启用的属性。

当属性的所有者对象不是null时,绑定有效,但如果所有者对象为null,则绑定不会执行任何操作(转换器不会被调用,{{3不使用)。

以下是一些重现问题的最小示例代码:


Window1.xaml.cs:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;

namespace NPCPropertyPath
{
    public abstract class VMBase : INotifyPropertyChanged
    {
        protected void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (e == null) {
                throw new ArgumentNullException("e");
            }

            if (PropertyChanged != null) {
                PropertyChanged(this, e);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public partial class Window1 : Window
    {
        private class MyConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value == null) {
                    return null;
                } else if (value is int) {
                    return (((int)value) + 15).ToString();
                } else {
                    return "no int";
                }
            }

            object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }

        private class InnerVM : VMBase
        {
            private int myValue;

            public int MyValue {
                get {
                    return myValue;
                }
                set {
                    if (myValue != value) {
                        myValue = value;
                        OnPropertyChanged("MyValue");
                    }
                }
            }
        }

        private class OuterVM : VMBase
        {
            private InnerVM thing;

            public InnerVM Thing {
                get {
                    return thing;
                }
                set {
                    if (thing != value) {
                        thing = value;
                        OnPropertyChanged("Thing");
                    }
                }
            }
        }

        private readonly OuterVM vm = new OuterVM();

        public Window1()
        {
            InitializeComponent();

            var txt = new TextBlock();
            txt.SetBinding(TextBlock.TextProperty,
                           new Binding("Thing.MyValue") {
                            Source = vm,
                            Mode = BindingMode.OneWay,
                            Converter = new MyConverter(),
                            TargetNullValue = "(error)"
                           });
            container.Content = txt;

            var txt2 = new TextBlock();
            txt2.SetBinding(TextBlock.TextProperty,
                            new Binding("Thing") {
                                Source = vm,
                                Mode = BindingMode.OneWay,
                                Converter = new MyConverter(),
                                TargetNullValue = "(error)"
                            });
            container2.Content = txt2;
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            vm.Thing = new InnerVM();
            vm.Thing.MyValue += 10;
        }
    }
}

Window1.xaml:

<Window x:Class="NPCPropertyPath.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="NPCPropertyPath" Height="300" Width="300">
    <StackPanel>
        <Button Content="Change value" Click="Button_Click"/>
        <ContentControl Name="container"/>
        <ContentControl Name="container2"/>
    </StackPanel>
</Window>

当然,这是我的真实应用程序的一个显着简化的形式,其中有相当多的事情发生并且所涉及的类没有被塞在两个文件中(甚至在同一个程序集中)所有人都能看到对方。

此示例显示一个带有按钮和两个内容控件的窗口。每个内容控件都包含TargetNullValue,其TextBlock绑定到视图模型中的属性。视图模型实例(类型OuterVM)被分配给每个绑定的Text property

OuterVM实施INotifyPropertyChanged;它有Thing类型的属性InnerVM(也实现了INotifyPropertyChanged),后者又有一个属性MyValue

第一个文本块绑定到Thing.MyValue,第二个文本块绑定到Thing。两个绑定都有一个转换器集,以及TargetNullValue属性的值,如果目标属性为null,则该值应显示在文本块上。

Thing实例的OuterVM属性最初为null。单击按钮时,会为该属性分配一些内容。

问题:只有第二个文本块最初显示任何内容。绑定到Thing.MyValue的那个既不调用转换器(通过设置断点来证明),也不使用TargetNullValue属性的值。

为什么呢?如果未分配Thing.MyValue,我怎样才能让第一个文本块显示默认值而不是Thing

1 个答案:

答案 0 :(得分:2)

为此,您不应使用TargetNullValue,应使用Binding的{​​{3}}属性。