延迟加载不可见的元素

本文关键字:元素 延迟加载 | 更新日期: 2023-09-27 17:49:24

我有一个案例,我有一个gridview/listbox/任何类型的项目控件,绑定到控件的项目数量非常大(很容易在5000+标记左右)。

这些项目中的每一个都需要从不同的web服务加载不同的属性。显然,让web服务一次处理这么多元素是不可能的。

我的问题是,是否有可能推迟加载,直到这些项目实际显示给用户?在这种情况下,用户向下滚动,尽管项目一直存在于集合中,但只有当它们实际物理呈现时才会处理它们。

我以前见过这样做,但我不记得具体在哪里了。这是一种情况,许多股票报价在一个绑定到gridview的集合中,但是它们的属性(价格等)是空的,直到它们第一次被显示(通过滚动到它们各自的位置)。

希望这对你(一些)有意义。

有什么好主意吗?

延迟加载不可见的元素

我会尝试延迟加载和异步加载的组合:
使用虚拟化列表控件。为您的项目创建一个ViewModel,并用ViewModel的实例填充您的列表(每行一个)。

在您的ViewModel中,创建具有默认值的属性,该属性向用户显示数据尚未加载。第一次访问这些属性中的一个,触发异步加载数据,并在接收到实际数据时触发INotifyPropertyChanged

这将给用户一个很好的体验,大多数棘手的工作将通过虚拟化列表完成(在WPF中,这是ListBox, ListView, DataGrid…)。希望对你有所帮助。

class LineItemVM : INotifyPropertyChanged{
  bool   m_loadingTriggered;
  string m_name="Loading...";
  string m_anotherProperty="Loading...";

  public string Name{
     get{
       TriggerLoadIfNecessary(); // Checks if data must be loaded
       return m_name;
     }
  }
  public string AnotherProperty{
     get{
       TriggerLoadIfNecessary(); // Checks if data must be loaded
       return m_anotherProperty;
     }
  }

  void TriggerLoadIfNecessary(){        
     if(!m_loadingTriggered){
       m_loadingTriggered=true;
       // This block will called before your item will be displayed
       //  Due to the m_loadingTriggered-member it is called only once.
       // Start here the asynchronous loading of the data
       // In virtualizing lists, this block is only called if the item
       //  will be visible to the user (he scrolls to this item)
       LoadAsync();
     }
  }
  ...
<<p> 附加逻辑/strong>作为一个想法,您还可以创建一个外部异步加载线程,它在后台加载所有数据,但有一个应该以更高优先级加载的项目列表。概念与上面的示例相同,但是TriggerLoadIfNecessary -方法不是从ViewModel-item加载数据,而是将项目添加到高优先级列表中,以便首先加载潜在的可见元素。哪个版本更适合的问题取决于列表的用法。如果用户可能会使用完整的列表,并且无法快速导航,那么这个扩展版本会更好。否则,原始版本可能更好。

这是一个事件,当用户滚动到最后一屏数据时将通知:

using System.Windows;
using System.Windows.Controls;
public static class ScrollViewer
{
    public static readonly RoutedEvent LastPageEvent = EventManager.RegisterRoutedEvent(
        "LastPage",
        RoutingStrategy.Bubble,
        typeof(RoutedEventHandler),
        typeof(ScrollViewer));
    private static readonly RoutedEventArgs EventArgs = new RoutedEventArgs(LastPageEvent);
    static ScrollViewer()
    {
        EventManager.RegisterClassHandler(
            typeof(System.Windows.Controls.ScrollViewer),
            System.Windows.Controls.ScrollViewer.ScrollChangedEvent,
            new ScrollChangedEventHandler(OnScrollChanged));
    }
    public static void AddLastPageHandler(UIElement e, RoutedEventHandler handler)
    {
        e.AddHandler(LastPageEvent, handler);
    }
    public static void RemoveLastPageHandler(UIElement e, RoutedEventHandler handler)
    {
        e.RemoveHandler(LastPageEvent, handler);
    }
    private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (e.ViewportHeight == 0 || e.VerticalOffset == 0)
        {
            return;
        }
        var verticalSpaceLeft = e.ExtentHeight - e.VerticalOffset;
        if (verticalSpaceLeft < 2 * e.ViewportHeight)
        {
            var scrollViewer = (System.Windows.Controls.ScrollViewer)sender;
            scrollViewer.RaiseEvent(EventArgs);
        }
    }
}