我有一个ItemsControl
绑定到ICollectionView
,并应用了过滤器。 ItemsControl
的{{1}}具有一个绑定,该绑定(1)引用ItemsTemplate
的{{1}}上的属性,而(2)使用ItemsControl
。当为绑定属性(1)引发DataContext
事件时,(从过滤器中)删除的所有项目仍将对其转换器进行重新评估。然后,传递的{DisconnectedItem}导致IValueConverter
。
处理这些DisconnectedItems的首选方法是什么?
可以肯定的是,只有在PropertyChanged
有一个InvalidCastException
且孩子具有可以从外部触发的绑定的情况下,才会发生这种情况。 DataTemplate
,例如在相同情况下的ContentControl
已被正确处理,即不会扔进转换器中。
MCVE
复制步骤:
ContentControl
)请注意,这只是一个示例项目。我不是要通过以下任一方法来阻止TextBlock
的变通办法:
OnPropertyChanged()
的电话{DisconnectedItem}
中的OnPropertyChanged();
绑定的RelativeSource
代码
IValueConverter
XAML
DataTemplate
答案 0 :(得分:0)
我发现一个特别有见地的帖子是Sam Bent的this answer。
这是有关{DisconnectedItem}的故事。
{DisconnectedItem}是WPF 4.0中新增的哨兵对象。当从视觉树中删除容器时,ItemsControl会将其容器之一的DataContext设置为{DisconnectedItem}。发生这种情况的主要原因是:(a)从基础集合中删除了相应的项目;(b)在屏幕外滚动并重新虚拟化了容器。
数据绑定引擎将{DisconnectedItem}识别为“停止侦听事件,但应尽可能少地监听”。这样可以避免断开连接的容器浪费CPU时间来尝试对属性更改事件做出反应。并避免由于断开连接的容器(您认为已经消失并忘记了)而采取的措施在主树中可见副作用,因此不会引起意外。
使用DataContext的绑定应该停止执行任何操作。不应调用任何转换器。
(...)
您对{DisconnectedItem}被传递给转换器的说法感到困惑。正如我在(3)中提到的那样,这不应该发生。除非您具有一种不寻常的绑定类型,否则它会在使用显式源(ElementName,AncestorType,Source =)时读取某些元素的DataContext。
解决方案1-您可以check the incoming object to see if it is of the sentinel type
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = CollectionViewSource.GetDefaultView(GetItems());
ApplyFilterCommand = new DelegateCommand(ApplyFilter);
CloseShopCommand = new DelegateCommand(CloseShop);
IsOpen = true;
}
public ICollectionView Items { get; }
private bool _isOpen;
public bool IsOpen
{
get { return _isOpen; }
set { _isOpen = value; OnPropertyChanged(nameof(IsOpen)); }
}
public ICommand ApplyFilterCommand { get; }
private void ApplyFilter(object parameter)
{
Items.Filter = o => ((string)o).Contains("o");
Items.Refresh();
}
public ICommand CloseShopCommand { get; }
private void CloseShop(object parameter)
{
IsOpen = false;
}
private static IList<string> GetItems()
{
var result = new List<string>
{
"Foo", "Bar", "Zoo", "Baz", "Rof", "Far"
};
return result;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class DelegateCommand : ICommand
{
readonly Action<object> _execute;
public DelegateCommand(Action<object> execute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
public class ToDisplayStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var txt = (string)values[0];
var isOpen = (bool)values[1];
return isOpen ? txt : "Closed for the day";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
此方法的缺点是,对转换器的调用数量随<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:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ToDisplayStringConverter x:Key="ToDisplayStringConverter"/>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Apply filter" Command="{Binding ApplyFilterCommand}" Margin="10"/>
<Button Content="Close Shop" Command="{Binding CloseShopCommand}" Margin="10"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding Items}" Width="120">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label>
<Label.Content>
<MultiBinding Converter="{StaticResource ToDisplayStringConverter}">
<Binding Path="." Mode="OneTime" />
<Binding Path="DataContext.IsOpen" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}" />
</MultiBinding>
</Label.Content>
</Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
的数量而增加,实际上可能会变得非常大。鉴于整个问题都源于以下事实:只要这些项目引用了父级上下文,就无法正确处理它们。
解决方案2-使用行为来设置public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == BindingOperations.DisconnectedSource) return null;
var txt = (string)values[0];
var isOpen = (bool)values[1];
return isOpen ? txt : "Closed for the day";
}
的{{1}}
相对于附加属性,我更喜欢使用Blend行为(要求System.Windows.Interactivity),因为它们提供了释放任何附加处理程序的便捷方法。
{DisconnectedItem}
一些注意事项:
ContentControl
被调用为时已晚,即在Content
之后被调用public class ReleaseOnDisconnectedBehavior : Behavior<ContentControl>
{
protected override void OnAttached()
{
AssociatedObject.IsVisibleChanged += OnVisibleChanged;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.IsVisibleChanged -= OnVisibleChanged;
base.OnDetaching();
}
private void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (AssociatedObject.DataContext == BindingOperations.DisconnectedSource)
{
AssociatedObject.Content = BindingOperations.DisconnectedSource;
}
}
}
无效,因为该对象不再是数据绑定对象(参考remarks)AssociatedObject.Unloaded
设置为PropertyChanged
而不是ClearAllBindings
(参考not everyone appreciates a null databind)将行为应用于Content
中的DisconnectedSource
。
null
当ContentControl
从可视树中删除容器时,其DataTemplate
将被设置为{DisconnectedItem}。