虚拟化和SelectionChanged事件

时间:2018-01-31 16:05:04

标签: c# wpf virtualization selectionchanged

我正在使用SelectionChanged的{​​{1}}事件,但它"无法正常工作"。

这是repro:

ListBox

和xaml:

public partial class MainWindow : Window
{
    readonly List<Item> _items = new List<Item>
    {
        new Item(),
        ... // add 20 more, to have selected item outside of visible region
        new Item(),
        new Item { IsSelected = true },
        new Item(),
    };

    void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
        Debug.WriteLine($"Changed {e.AddedItems.Count}/{e.RemovedItems.Count}");

    void button_Click(object sender, RoutedEventArgs e) =>
        listBox.ItemsSource = listBox.ItemsSource == null ? _items : null;
}

public class Item
{
    public bool IsSelected { get; set; }
}

1。禁用虚拟化

添加到列表:

<Grid>
    <ListBox x:Name="listBox" SelectionChanged="listBox_SelectionChanged">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="IsSelected" Value="{Binding IsSelected}" />
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
    <Button Content="Test" HorizontalAlignment="Right" VerticalAlignment="Bottom"
            Margin="10" Click="button_Click" />
</Grid>

单击按钮将生成输出。凉。

2。 VirtualizationMode =&#34;标准&#34;

删除该行,默认情况下VirtualizingPanel.IsVirtualizing="False" 将使用&#34;标准&#34;虚拟化:

未触发事件。我需要滚动到所选项目以触发事件。

3。 VirtualizationMode =&#34;回收&#34;

将虚拟化更改为:

ListBox

WTF?即使滚动也不会触发事件。

问题:如何让VirtualizingPanel.VirtualizationMode="Recycling" 事件在最佳效果模式下正常 模式?

1 个答案:

答案 0 :(得分:2)

使用虚拟化,如果某个项目没有与之关联的容器(ListBoxItem),那么就没有应用ItemContainerStyle的容器。这意味着在项目滚动到视图中之前,不会应用IsSelected绑定。在设置该属性之前,不会发生选择更改,并且不会引发SelectionChanged

  

如何让SelectionChanged事件在最高性能模式下正常工作而无需像“标准”模式那样滚动?

可以说 *是* 正常工作。如果从MVVM角度处理此问题,则无需依赖UI元素中的事件。在您的模型中自己跟踪项目选择。您可以使用这样的实用程序类:

public interface ISelectable
{
    bool IsSelected { get; set; }
}

public class ItemEventArgs<T> : EventArgs
{
    public T Item { get; }
    public ItemEventArgs(T item) => this.Item = item;
}

public class SelectionTracker<T> where T : ISelectable
{
    private readonly ObservableCollection<T> _items;
    private readonly ObservableCollection<T> _selectedItems;
    private readonly ReadOnlyObservableCollection<T> _selectedItemsView;
    private readonly HashSet<T> _trackedItems;
    private readonly HashSet<T> _fastSelectedItems;

    public SelectionTracker(ObservableCollection<T> items)
    {
        _items = items;
        _selectedItems = new ObservableCollection<T>();
        _selectedItemsView = new ReadOnlyObservableCollection<T>(_selectedItems);
        _trackedItems = new HashSet<T>();
        _fastSelectedItems = new HashSet<T>();
        _items.CollectionChanged += OnCollectionChanged;
    }

    public event EventHandler<ItemEventArgs<T>> ItemSelected; 
    public event EventHandler<ItemEventArgs<T>> ItemUnselected; 

    public ReadOnlyObservableCollection<T> SelectedItems => _selectedItemsView;

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                if (e.NewItems == null)
                    goto default;
                AddItems(e.NewItems.OfType<T>());
                break;

            case NotifyCollectionChangedAction.Remove:
                if (e.OldItems == null)
                    goto default;
                RemoveItems(e.OldItems.OfType<T>());
                break;

            case NotifyCollectionChangedAction.Replace:
                if (e.OldItems == null || e.NewItems == null)
                    goto default;
                RemoveItems(e.OldItems.OfType<T>());
                AddItems(e.NewItems.OfType<T>());
                break;

            case NotifyCollectionChangedAction.Move:
                break;

            default:
                Refresh();
                break;
        }
    }

    public void Refresh()
    {
        RemoveItems(_trackedItems);
        AddItems(_items);
    }

    private void AddItems(IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            var observableItem = item as INotifyPropertyChanged;
            if (observableItem != null)
                observableItem.PropertyChanged += OnItemPropertyChanged;

            _trackedItems.Add(item);

            UpdateItem(item);
        }
    }

    private void RemoveItems(IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            var observableItem = item as INotifyPropertyChanged;
            if (observableItem != null)
                observableItem.PropertyChanged -= OnItemPropertyChanged;

            _trackedItems.Remove(item);

            UpdateItem(item);
        }
    }

    private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (sender is T item)
            UpdateItem(item);
    }

    private void UpdateItem(T item)
    {
        if (item?.IsSelected == true && _trackedItems.Contains(item))
        {
            if (_fastSelectedItems.Add(item))
            {
                _selectedItems.Add(item);
                this.ItemSelected?.Invoke(this, new ItemEventArgs<T>(item));
            }
        }
        else
        {
            if (_fastSelectedItems.Remove(item))
            {
                _selectedItems.Remove(item);
                this.ItemUnselected?.Invoke(this, new ItemEventArgs<T>(item));
            }
        }
    }
}

创建ObservableCollection个项目时,请为该集合实例化SelectionTracker。然后订阅ItemSelectedItemUnselected以处理各个选择更改,或者订阅SelectedItems.CollectionChanged。如果您不关心能够作为集合访问SelectedItems,那么您可以摆脱_selectedItems_selectedItemsView并避免一些列表删除开销。

  

[有VirtualizationMode="Recycling"] WTF?即使滚动也不会触发事件。

嗯,这是一个奇怪的。我认为没有理由说这不应该在这种情况下起作用,但我也许可以理解为什么它总是不起作用。理论上,只要容器被“回收”并且其DataContext被分配了一个新项目,IsSelected绑定就会更新。如果容器的先前分配的项目已选择 ,则可能不会触发属性更改,因此事件可能不会触发。但在您的示例中似乎并非如此。可能是如何实施回收的错误或意外后果。

请勿使用IsSelected来管理选择。

我认为最重要的是使用ListBoxItem.IsSelected来* 设置 *,选择是不可靠的;只应信任反映是否选择了给定的容器。它真正用于样式和模板触发器,以便他们可以知道是否将容器呈现为选定状态。从来没有打算管理选择,并且以这种方式使用它是错误的,因为它代表容器的选择状态而不是其关联的数据项。因此,它只适用于最天真,性能最差的场景,其中每个项目始终与其自己的容器相关联(无虚拟化)。