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
ます。Thing
typeのプロパティInnerVM
( も実装INotifyPropertyChanged
) があり、次にプロパティ がありますMyValue
。
最初のテキスト ブロックは にバインドされThing.MyValue
、2 番目のテキスト ブロックは にのみバインドされThing
ます。両方のバインディングには、コンバータ セットとTargetNullValue
、ターゲット プロパティが の場合にテキスト ブロックに表示されるプロパティの値がありますnull
。
インスタンスのThing
プロパティは initialです。ボタンをクリックすると、そのプロパティに何かが割り当てられます。OuterVM
null
問題:最初は 2 番目のテキスト ブロックだけが表示されます。どちらにもバインドされていないものは、コンバーターを呼び出したり (ブレークポイントを設定することで証明されます)、プロパティThing.MyValue
の値を使用したりしません。TargetNullValue
なんで?また、割り当てられていないThing.MyValue
間ではなく、最初のテキストブロックにデフォルト値を表示するにはどうすればよいですか?Thing