DataTemplate IValueConverter中的DisconnectedItem

时间:2019-07-14 15:00:29

标签: c# wpf data-binding datatemplate itemscontrol

我有一个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

1 个答案:

答案 0 :(得分:0)

我发现一个特别有见地的帖子是Sam Bent的this answer

  

这是有关{DisconnectedItem}的故事。

     
      
  1. {DisconnectedItem}是WPF 4.0中新增的哨兵对象。当从视觉树中删除容器时,ItemsControl会将其容器之一的DataContext设置为{DisconnectedItem}。发生这种情况的主要原因是:(a)从基础集合中删除了相应的项目;(b)在屏幕外滚动并重新虚拟化了容器。

  2.   
  3. 数据绑定引擎将{DisconnectedItem}识别为“停止侦听事件,但应尽可能少地监听”。这样可以避免断开连接的容器浪费CPU时间来尝试对属性更改事件做出反应。并避免由于断开连接的容器(您认为已经消失并忘记了)而采取的措施在主树中可见副作用,因此不会引起意外。

  4.   
  5. 使用DataContext的绑定应该停止执行任何操作。不应调用任何转换器。

  6.   
     

(...)

     

您对{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}。