3

INotifyPropertyChanged複数レベルのプロパティ パスを使用して、依存関係プロパティを有効なプロパティにバインドしようとしています。

プロパティの所有者オブジェクトが ではないnull場合、バインディングは機能しますが、所有者オブジェクトがnullである場合、バインディングは何もしません (コンバーターは呼び出されTargetNullValueず、使用されません)。

問題を再現するための最小限のサンプル コードを次に示します。


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>

もちろん、これは実際のアプリケーションを大幅に単純化したものであり、さらに多くのことが行われており、関連するクラスが 2 つのファイル (または同じアセンブリ) に詰め込まれているわけではありません。

このサンプルは、ボタンと 2 つのコンテンツ コントロールを含むウィンドウを表示します。各コンテンツ コントロールには、TextBlockそのTextプロパティがビュー モデルのプロパティにバインドされている が含まれています。ビュー モデル インスタンス (タイプOuterVM) は、各バインディングのSourceプロパティに割り当てられます。

OuterVM実装しINotifyPropertyChangedます。ThingtypeのプロパティInnerVM( も実装INotifyPropertyChanged) があり、次にプロパティ がありますMyValue

最初のテキスト ブロックは にバインドされThing.MyValue、2 番目のテキスト ブロックは にのみバインドされThingます。両方のバインディングには、コンバータ セットとTargetNullValue、ターゲット プロパティが の場合にテキスト ブロックに表示されるプロパティの値がありますnull

インスタンスのThingプロパティは initialです。ボタンをクリックすると、そのプロパティに何かが割り当てられます。OuterVMnull

問題:最初は 2 番目のテキスト ブロックだけが表示されます。どちらにもバインドされていないものは、コンバーターを呼び出したり (ブレークポイントを設定することで証明されます)、プロパティThing.MyValueの値を使用したりしません。TargetNullValue

なんで?また、割り当てられていないThing.MyValue間ではなく、最初のテキストブロックにデフォルト値を表示するにはどうすればよいですか?Thing

4

1 に答える 1