我有大量项目绑定到ListBox
,VirtualizingStackPanel
设置为ItemsPanel
。当用户滚动并创建项容器时,我做了一些工作来用数据填充项目(使用数据库查询)。如果用户滚动非常快,它会产生大量的请求,这些请求往往会使事情陷入困境。我想要做的是检测项目在视口外滚动的时间,以便取消相应的请求。
以下是我迄今为止尝试过的方法,以及为什么它们没有奏效:
覆盖VirtualizingStackPanel.OnCleanUpVirtualizedItem
。该
问题是这个方法似乎很久以后就会被调用
而当项目实际离开屏幕时。取消我的请求
在这种方法中并没有太大的好处,因为它发生得太晚了。
打开容器回收利用
VirtualizationMode.Recycling
。此事件导致该项目
容器的DataContext
要更改,但项容器本身是
重复使用。 DataContextChanged
事件会立即发生
项目超出了视野,所以在这方面是好的。问题
是容器回收产生了很多副作用,在我的
测试,总体来说是一个小小的车。我宁愿不使用它。
是否有一个好的低级方法,例如挂钩布局事件,这可以给我一个关于项目何时超出视图的确定性答案?也许在ScrollViewer
级别?
答案 0 :(得分:2)
这是一个粗略的解决方案,我认为可以实现您正在寻找的东西。我通过侦听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>
答案 1 :(得分:0)
在viewmodel端,您可以观看与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.Recycling
,ListBox可能会推迟容器销毁(以及分离),直到用户停止滚动。这可以扩展内存消耗,特别是如果ItemTemplate很复杂(并且也不会取消数据库查询)。
答案 2 :(得分:-1)
您可以尝试添加。
scrollviewer.veriticalscroll =“auto”
这将导致它仅滚动列表框中已有的项目数量