2

我有大量绑定到 a 的项目集合ListBox,其中一个VirtualizingStackPanel集合作为它的ItemsPanel. 随着用户滚动和项目容器的创建,我做了一些工作来用数据填充项目(使用数据库查询)。如果用户滚动速度非常快,它会产生大量请求,从而使事情陷入困境。我想做的是检测项目何时滚动到视口之外,因此我可以取消其相应的请求。

以下是我迄今为止尝试过的方法,以及为什么它们没有奏效:

  1. 覆盖VirtualizingStackPanel.OnCleanUpVirtualizedItem。问题是这个方法的调用似乎比项目实际离开屏幕的时间晚得多。在这种方法中取消我的请求并没有多大好处,因为它发生得太晚了。

  2. 使用 开启容器回收 VirtualizationMode.Recycling。此事件导致项目容器DataContext发生更改,但项目容器本身被重用。DataContextChanged当一个项目超出视野时,该事件立即发生,因此在这方面很好。问题是容器回收会产生很多副作用,并且在我的测试中,总体上有点错误。我宁愿不使用它。

是否有一个好的低级方法,例如挂钩布局事件,可以给我一个关于项目何时超出视图的确定性答案?也许在ScrollViewer水平上?

4

3 回答 3

4

这是一个粗略的解决方案,我认为可以满足您的需求。我通过侦听 XAML 中的加载事件来获取虚拟化堆栈面板。如果我在生产代码中这样做,我可能会将其纳入可重用的附加行为,而不是在代码隐藏中抛出一堆代码。

public partial class MainWindow
{
    private VirtualizingStackPanel _panel;
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MyViewModel();
    }

    private IList<ChildViewModel> _snapshot = new List<ChildViewModel>();

    private void OnPanelLoaded(object sender, RoutedEventArgs eventArgs)
    {
        _panel = (VirtualizingStackPanel)sender;
        UpdateSnapshot();
        _panel.ScrollOwner.ScrollChanged += (s,e) => UpdateSnapshot();
    }

    private void UpdateSnapshot()
    {
        var layoutBounds = LayoutInformation.GetLayoutSlot(_panel);

        var onScreenChildren = 
            (from visualChild in _panel.GetChildren()
             let childBounds = LayoutInformation.GetLayoutSlot(visualChild)
             where layoutBounds.Contains(childBounds) || layoutBounds.IntersectsWith(childBounds)
             select visualChild.DataContext).Cast<ChildViewModel>().ToList();            

        foreach (var removed in _snapshot.Except(onScreenChildren))
        {
            // TODO: Cancel pending calculations.
            Console.WriteLine("{0} was removed.", removed.Value);
        }
        _snapshot = onScreenChildren;
    }
}

请注意,这里并没有真正可以用来查找屏幕上的子级的属性,因此我们查看父级与子级的布局边界,以确定屏幕上的子级。该代码使用扩展方法来获取可视树中项目的可视子项,包括以下内容:

public static class MyVisualTreeHelpers
{
    public static IEnumerable<FrameworkElement> GetChildren(this DependencyObject dependencyObject)
    {
        var numberOfChildren = VisualTreeHelper.GetChildrenCount(dependencyObject);
        return (from index in Enumerable.Range(0, numberOfChildren)
                select VisualTreeHelper.GetChild(dependencyObject, index)).Cast<FrameworkElement>();
    }
}

这段代码使用我创建的一个非常基本的视图模型层次结构来测试它。我将包括它以防万一它有助于理解其他代码:

public class MyViewModel
{
    public MyViewModel()
    {
        Children = new ObservableCollection<ChildViewModel>(GenerateChildren());
    }

    public ObservableCollection<ChildViewModel> Children { get; set; }

    private static IEnumerable<ChildViewModel> GenerateChildren()
    {
        return from value in Enumerable.Range(1, 1000)
               select new ChildViewModel {Value = value};
    }
}

public class ChildViewModel
{
    public int Value { get; set; }
}

XAML:

<Window x:Class="WpfTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfTest="clr-namespace:WpfTest"
        Title="MainWindow" Height="500" Width="500">
    <ListBox ItemsSource="{Binding Children}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel Loaded="OnPanelLoaded" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>

        <ListBox.ItemTemplate>
            <DataTemplate DataType="wpfTest:ChildViewModel">
                <TextBlock Text="{Binding Value}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Window>
于 2014-01-13T04:05:57.870 回答
0

在视图模型方面,您可以观看 INotifyPropertyChanged 事件的附加和分离:

public event PropertyChangedEventHandler PropertyChanged
{
    add
    {
        if(this.InternalPropertyChanged == null)
            Console.WriteLine("COMING INTO VIEW");
        this.InternalPropertyChanged += value;
    }

    remove
    {
        this.InternalPropertyChanged -= value;
        if(this.InternalPropertyChanged == null)
            Console.WriteLine("OUT OF VIEW");
    }
}

private event PropertyChangedEventHandler InternalPropertyChanged;

注意:没有VirtualizationMode.RecyclingListBox 可能会推迟容器销毁(因此分离),直到用户停止滚动。这会大量增加内存消耗,尤其是在 ItemTemplate 很复杂的情况下(并且也不会取消您的数据库查询)。

于 2017-02-17T12:02:39.227 回答
-1

您可以尝试添加。
scrollviewer.veriticalscroll = "auto" 这将导致它只滚动列表框中已有的项目数量

于 2014-01-13T00:11:40.313 回答