未针对 CompositeCollection 和嵌套 CollectionViewSource.Source 绑定正确引发依赖项 PropertyChangedHandler
Dependency PropertyChangedHandler not raised properly for CompositeCollection & nested CollectionViewSource.Source bindings
这真的让我很震惊......
上下文
我目前正在开发一个应用程序,我需要将多个集合(Receipt.Contact.Addresses
、Receipt.Contact.MainAddress
通过转换器转换为集合)合并为一个组合框的单一来源 (Receipt.BillingAddress
) .
问题
实际应用程序已将 Receipt.BillingAddress
绑定到具有所述 CompositeCollection
的 ComboBox
的 SelectedItem
属性。
更改 Receipt.Contact
然后将删除 Receipt.BillingAddress
,因为 Selector
就是这样工作的。
然而,由于异步 IO(服务器接收空更新,发送空更新,服务器接收另一个更新,...),这引入了随机行为,也就是问题。
理论上,这可以通过每次分离和重新附加绑定来解决,实际集合发生变化(因此 ItemsSourceAttached)
遗憾的是,这不起作用,因为 PropertyChangedHandler
只会在第一次更改时触发。
奇怪的东西
如果 CollectionViewSource.Source
绑定中没有额外的级别(Receipt.Contact.Addresses
vs Addresses
)
,这完全有效
如何重现(最小可行示例)
为了重现此行为,我创建了以下 MVE,其中包含 3 个 类(Window、AttachedProperty 和 SomeContainer)和一个 XAML 文件(Window):
附加属性
public static class ItemsSourceAttached
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.RegisterAttached(
nameof(Selector.ItemsSource),
typeof(IEnumerable),
typeof(ItemsSourceAttached),
new FrameworkPropertyMetadata(null, ItemsSourcePropertyChanged)
);
public static void SetItemsSource(Selector element, IEnumerable value)
{
element.SetValue(ItemsSourceProperty, value);
}
public static IEnumerable GetItemsSource(Selector element)
{
return (IEnumerable)element.GetValue(ItemsSourceProperty);
}
static void ItemsSourcePropertyChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Attached Changed!");
if (element is Selector target)
{
target.ItemsSource = e.NewValue as IEnumerable;
}
}
}
一些容器
public class SomeContainer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string[] Data1 { get; }
public string[] Data2 { get; }
public SomeContainer(string[] data1, string[] data2)
{
this.Data1 = data1;
this.Data2 = data2;
}
}
Window (C#) 和 DataContext(为简单起见)
public partial class CompositeCollectionTest : Window, INotifyPropertyChanged
{
public SomeContainer Data
{
get => this._Data;
set
{
this._Data = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Data)));
}
}
private SomeContainer _Data;
// Not allowed to be NULLed on ItemsSource change
public string SelectedItem
{
get => this._SelectedItem;
set
{
this._SelectedItem = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.SelectedItem)));
}
}
private string _SelectedItem;
public bool SomeProperty => false;
public event PropertyChangedEventHandler PropertyChanged;
public CompositeCollectionTest()
{
this.InitializeComponent();
var descriptor = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(Selector));
descriptor.AddValueChanged(this.MyComboBox, (sender, e) => {
MessageBox.Show("Property Changed!");
});
}
static int i = 0;
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Data = new SomeContainer(new string[]
{
$"{i}-DATA-A-1",
$"{i}-DATA-A-2",
$"{i}-DATA-A-3"
},
new string[]
{
$"{i}-DATA-B-1",
$"{i}-DATA-B-2",
$"{i}-DATA-B-3"
});
i++;
}
}
Window (XAML):
<Window x:Class="WpfTest.CompositeCollectionTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTest"
mc:Ignorable="d"
Title="CompositeCollectionTest"
Height="450" Width="800"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Window.Resources>
<CollectionViewSource x:Key="ViewSource1" Source="{Binding Data.Data1}"/>
<CollectionViewSource x:Key="ViewSource2" Source="{Binding Data.Data2}"/>
</Window.Resources>
<StackPanel>
<ComboBox x:Name="MyComboBox" SelectedItem="{Binding SelectedItem}">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="False">
<Setter Property="local:ItemsSourceAttached.ItemsSource">
<Setter.Value>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource ViewSource1}}"/>
<CollectionContainer Collection="{Binding Source={StaticResource ViewSource2}}"/>
</CompositeCollection>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<Button Content="Generate" Click="Button_Click"/>
</StackPanel>
</Window>
谢谢你的时间。
我真的希望有人能指出我似乎无法找到的明显错误...
CollectionView
非常适合对绑定集合进行过滤/分组/排序。一旦开始即时交换 ItemsSource
,您将需要 keep everything in sync.
但是,鉴于您的用例需求:
- 自定义数据收集以组成集合
- 交换时取消绑定\绑定行为
- 更多地控制
SelectedItem
您可以改为在视图模型和视图之间引入额外的抽象,如 所述。我为您最初的收据联系人问题编写了一个演示。
namespace WpfApp.Models
{
public interface IAddress
{
string Street { get; }
}
public class Address : IAddress
{
public Address(string street)
{
Street = street;
}
public string Street { get; }
}
public class Contact
{
public Contact(string name, IAddress mainAddress, IAddress[] addresses)
{
Name = name;
MainAddress = mainAddress;
Addresses = addresses;
}
public string Name { get; }
public IAddress MainAddress { get; }
public IAddress[] Addresses { get; }
}
}
接下来,额外的 ItemsContext
抽象和 ReceiptViewModel
.
namespace WpfApp.ViewModels
{
public class ItemsContext : ViewModelBase
{
public ItemsContext(Contact contact)
{
if (contact == null) throw new ArgumentNullException(nameof(contact));
// Compose the collection however you like
Items = new ObservableCollection<IAddress>(contact.Addresses.Prepend(contact.MainAddress));
DisplayMemberPath = nameof(IAddress.Street);
SelectedItem = Items.First();
}
public ObservableCollection<IAddress> Items { get; }
public string DisplayMemberPath { get; }
private IAddress selectedItem;
public IAddress SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged();
// Prevent XAML designer from tearing down VS
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
MessageBox.Show($"Billing address changed to {selectedItem.Street}");
}
}
}
}
public class ReceiptViewModel : ViewModelBase
{
public ReceiptViewModel()
{
Contacts = new ObservableCollection<Contact>(FetchContacts());
SelectedContact = Contacts.First();
}
public ObservableCollection<Contact> Contacts { get; }
private Contact selectedContact;
public Contact SelectedContact
{
get { return selectedContact; }
set
{
selectedContact = value;
SelectedContext = new ItemsContext(value);
OnPropertyChanged();
}
}
private ItemsContext selectedContext;
public ItemsContext SelectedContext
{
get { return selectedContext; }
set
{
selectedContext = value;
OnPropertyChanged();
}
}
private static IEnumerable<Contact> FetchContacts() =>
new List<Contact>
{
new Contact("Foo", new Address("FooMain"), new Address[] { new Address("FooA"), new Address("FooB") }),
new Contact("Bar", new Address("BarMain"), new Address[] { new Address("BarA"), new Address("BarB") }),
new Contact("Zoo", new Address("ZooMain"), new Address[] { new Address("ZooA"), new Address("ZooB") }),
};
}
abstract public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
为了应用 ItemsContext
,我也选择使用附加的 属性,尽管您也可以选择子类 ComboBox
(或从 Selector
派生的任何东西) .
namespace WpfApp.Extensions
{
public class Selector
{
public static ItemsContext GetContext(DependencyObject obj) => (ItemsContext)obj.GetValue(ContextProperty);
public static void SetContext(DependencyObject obj, ItemsContext value) => obj.SetValue(ContextProperty, value);
public static readonly DependencyProperty ContextProperty =
DependencyProperty.RegisterAttached("Context", typeof(ItemsContext), typeof(Selector), new PropertyMetadata(null, OnItemsContextChanged));
private static void OnItemsContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (System.Windows.Controls.Primitives.Selector)d;
var ctx = (ItemsContext)e.NewValue;
if (e.OldValue != null) // Clean up bindings from previous context, if any
{
BindingOperations.ClearBinding(selector, System.Windows.Controls.Primitives.Selector.SelectedItemProperty);
BindingOperations.ClearBinding(selector, ItemsControl.ItemsSourceProperty);
BindingOperations.ClearBinding(selector, ItemsControl.DisplayMemberPathProperty);
}
selector.SetBinding(System.Windows.Controls.Primitives.Selector.SelectedItemProperty, new Binding(nameof(ItemsContext.SelectedItem)) { Source = ctx, Mode = BindingMode.TwoWay });
selector.SetBinding(ItemsControl.ItemsSourceProperty, new Binding(nameof(ItemsContext.Items)) { Source = ctx });
selector.SetBinding(ItemsControl.DisplayMemberPathProperty, new Binding(nameof(ItemsContext.DisplayMemberPath)) { Source = ctx });
}
}
}
结束视图。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp.ViewModels"
xmlns:ext="clr-namespace:WpfApp.Extensions"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:ReceiptViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Width" Value="150"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="0,0,0,20"/>
</Style>
</Window.Resources>
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Contact Name" />
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Contacts}" SelectedItem="{Binding SelectedContact}" DisplayMemberPath="Name" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Billing Address" />
<ComboBox Grid.Row="1" Grid.Column="1" ext:Selector.Context="{Binding SelectedContext}" />
</Grid>
</Window>
如果你 运行 演示你会看到切换上下文时没有 null
地址弹出,只是因为我们在上下文本身上实现 SelectedItem
(即抽象在视图模型和视图之间)。任何帐单地址更改 逻辑都可以很容易地注入或在上下文中实现。
我引用的另一个 post 强调存储状态,直到上下文再次激活,例如SelectedItem
。在此 post 中,我们即时创建 ItemsContext
,因为可能有很多联系人。当然,您可以随意调整它。
这真的让我很震惊......
上下文
我目前正在开发一个应用程序,我需要将多个集合(Receipt.Contact.Addresses
、Receipt.Contact.MainAddress
通过转换器转换为集合)合并为一个组合框的单一来源 (Receipt.BillingAddress
) .
问题
实际应用程序已将 Receipt.BillingAddress
绑定到具有所述 CompositeCollection
的 ComboBox
的 SelectedItem
属性。
更改 Receipt.Contact
然后将删除 Receipt.BillingAddress
,因为 Selector
就是这样工作的。
然而,由于异步 IO(服务器接收空更新,发送空更新,服务器接收另一个更新,...),这引入了随机行为,也就是问题。
理论上,这可以通过每次分离和重新附加绑定来解决,实际集合发生变化(因此 ItemsSourceAttached)
遗憾的是,这不起作用,因为 PropertyChangedHandler
只会在第一次更改时触发。
奇怪的东西
如果 CollectionViewSource.Source
绑定中没有额外的级别(Receipt.Contact.Addresses
vs Addresses
)
如何重现(最小可行示例)
为了重现此行为,我创建了以下 MVE,其中包含 3 个 类(Window、AttachedProperty 和 SomeContainer)和一个 XAML 文件(Window):
附加属性
public static class ItemsSourceAttached
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.RegisterAttached(
nameof(Selector.ItemsSource),
typeof(IEnumerable),
typeof(ItemsSourceAttached),
new FrameworkPropertyMetadata(null, ItemsSourcePropertyChanged)
);
public static void SetItemsSource(Selector element, IEnumerable value)
{
element.SetValue(ItemsSourceProperty, value);
}
public static IEnumerable GetItemsSource(Selector element)
{
return (IEnumerable)element.GetValue(ItemsSourceProperty);
}
static void ItemsSourcePropertyChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Attached Changed!");
if (element is Selector target)
{
target.ItemsSource = e.NewValue as IEnumerable;
}
}
}
一些容器
public class SomeContainer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string[] Data1 { get; }
public string[] Data2 { get; }
public SomeContainer(string[] data1, string[] data2)
{
this.Data1 = data1;
this.Data2 = data2;
}
}
Window (C#) 和 DataContext(为简单起见)
public partial class CompositeCollectionTest : Window, INotifyPropertyChanged
{
public SomeContainer Data
{
get => this._Data;
set
{
this._Data = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Data)));
}
}
private SomeContainer _Data;
// Not allowed to be NULLed on ItemsSource change
public string SelectedItem
{
get => this._SelectedItem;
set
{
this._SelectedItem = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.SelectedItem)));
}
}
private string _SelectedItem;
public bool SomeProperty => false;
public event PropertyChangedEventHandler PropertyChanged;
public CompositeCollectionTest()
{
this.InitializeComponent();
var descriptor = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(Selector));
descriptor.AddValueChanged(this.MyComboBox, (sender, e) => {
MessageBox.Show("Property Changed!");
});
}
static int i = 0;
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Data = new SomeContainer(new string[]
{
$"{i}-DATA-A-1",
$"{i}-DATA-A-2",
$"{i}-DATA-A-3"
},
new string[]
{
$"{i}-DATA-B-1",
$"{i}-DATA-B-2",
$"{i}-DATA-B-3"
});
i++;
}
}
Window (XAML):
<Window x:Class="WpfTest.CompositeCollectionTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTest"
mc:Ignorable="d"
Title="CompositeCollectionTest"
Height="450" Width="800"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Window.Resources>
<CollectionViewSource x:Key="ViewSource1" Source="{Binding Data.Data1}"/>
<CollectionViewSource x:Key="ViewSource2" Source="{Binding Data.Data2}"/>
</Window.Resources>
<StackPanel>
<ComboBox x:Name="MyComboBox" SelectedItem="{Binding SelectedItem}">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="False">
<Setter Property="local:ItemsSourceAttached.ItemsSource">
<Setter.Value>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource ViewSource1}}"/>
<CollectionContainer Collection="{Binding Source={StaticResource ViewSource2}}"/>
</CompositeCollection>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<Button Content="Generate" Click="Button_Click"/>
</StackPanel>
</Window>
谢谢你的时间。 我真的希望有人能指出我似乎无法找到的明显错误...
CollectionView
非常适合对绑定集合进行过滤/分组/排序。一旦开始即时交换 ItemsSource
,您将需要 keep everything in sync.
但是,鉴于您的用例需求:
- 自定义数据收集以组成集合
- 交换时取消绑定\绑定行为
- 更多地控制
SelectedItem
您可以改为在视图模型和视图之间引入额外的抽象,如
namespace WpfApp.Models
{
public interface IAddress
{
string Street { get; }
}
public class Address : IAddress
{
public Address(string street)
{
Street = street;
}
public string Street { get; }
}
public class Contact
{
public Contact(string name, IAddress mainAddress, IAddress[] addresses)
{
Name = name;
MainAddress = mainAddress;
Addresses = addresses;
}
public string Name { get; }
public IAddress MainAddress { get; }
public IAddress[] Addresses { get; }
}
}
接下来,额外的 ItemsContext
抽象和 ReceiptViewModel
.
namespace WpfApp.ViewModels
{
public class ItemsContext : ViewModelBase
{
public ItemsContext(Contact contact)
{
if (contact == null) throw new ArgumentNullException(nameof(contact));
// Compose the collection however you like
Items = new ObservableCollection<IAddress>(contact.Addresses.Prepend(contact.MainAddress));
DisplayMemberPath = nameof(IAddress.Street);
SelectedItem = Items.First();
}
public ObservableCollection<IAddress> Items { get; }
public string DisplayMemberPath { get; }
private IAddress selectedItem;
public IAddress SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged();
// Prevent XAML designer from tearing down VS
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
MessageBox.Show($"Billing address changed to {selectedItem.Street}");
}
}
}
}
public class ReceiptViewModel : ViewModelBase
{
public ReceiptViewModel()
{
Contacts = new ObservableCollection<Contact>(FetchContacts());
SelectedContact = Contacts.First();
}
public ObservableCollection<Contact> Contacts { get; }
private Contact selectedContact;
public Contact SelectedContact
{
get { return selectedContact; }
set
{
selectedContact = value;
SelectedContext = new ItemsContext(value);
OnPropertyChanged();
}
}
private ItemsContext selectedContext;
public ItemsContext SelectedContext
{
get { return selectedContext; }
set
{
selectedContext = value;
OnPropertyChanged();
}
}
private static IEnumerable<Contact> FetchContacts() =>
new List<Contact>
{
new Contact("Foo", new Address("FooMain"), new Address[] { new Address("FooA"), new Address("FooB") }),
new Contact("Bar", new Address("BarMain"), new Address[] { new Address("BarA"), new Address("BarB") }),
new Contact("Zoo", new Address("ZooMain"), new Address[] { new Address("ZooA"), new Address("ZooB") }),
};
}
abstract public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
为了应用 ItemsContext
,我也选择使用附加的 属性,尽管您也可以选择子类 ComboBox
(或从 Selector
派生的任何东西) .
namespace WpfApp.Extensions
{
public class Selector
{
public static ItemsContext GetContext(DependencyObject obj) => (ItemsContext)obj.GetValue(ContextProperty);
public static void SetContext(DependencyObject obj, ItemsContext value) => obj.SetValue(ContextProperty, value);
public static readonly DependencyProperty ContextProperty =
DependencyProperty.RegisterAttached("Context", typeof(ItemsContext), typeof(Selector), new PropertyMetadata(null, OnItemsContextChanged));
private static void OnItemsContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (System.Windows.Controls.Primitives.Selector)d;
var ctx = (ItemsContext)e.NewValue;
if (e.OldValue != null) // Clean up bindings from previous context, if any
{
BindingOperations.ClearBinding(selector, System.Windows.Controls.Primitives.Selector.SelectedItemProperty);
BindingOperations.ClearBinding(selector, ItemsControl.ItemsSourceProperty);
BindingOperations.ClearBinding(selector, ItemsControl.DisplayMemberPathProperty);
}
selector.SetBinding(System.Windows.Controls.Primitives.Selector.SelectedItemProperty, new Binding(nameof(ItemsContext.SelectedItem)) { Source = ctx, Mode = BindingMode.TwoWay });
selector.SetBinding(ItemsControl.ItemsSourceProperty, new Binding(nameof(ItemsContext.Items)) { Source = ctx });
selector.SetBinding(ItemsControl.DisplayMemberPathProperty, new Binding(nameof(ItemsContext.DisplayMemberPath)) { Source = ctx });
}
}
}
结束视图。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp.ViewModels"
xmlns:ext="clr-namespace:WpfApp.Extensions"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:ReceiptViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Width" Value="150"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="0,0,0,20"/>
</Style>
</Window.Resources>
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Contact Name" />
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Contacts}" SelectedItem="{Binding SelectedContact}" DisplayMemberPath="Name" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Billing Address" />
<ComboBox Grid.Row="1" Grid.Column="1" ext:Selector.Context="{Binding SelectedContext}" />
</Grid>
</Window>
如果你 运行 演示你会看到切换上下文时没有 null
地址弹出,只是因为我们在上下文本身上实现 SelectedItem
(即抽象在视图模型和视图之间)。任何帐单地址更改 逻辑都可以很容易地注入或在上下文中实现。
我引用的另一个 post 强调存储状态,直到上下文再次激活,例如SelectedItem
。在此 post 中,我们即时创建 ItemsContext
,因为可能有很多联系人。当然,您可以随意调整它。