不可见元素的延迟加载

Posted

技术标签:

【中文标题】不可见元素的延迟加载【英文标题】:Lazy loading of non-visible elements 【发布时间】:2011-08-01 18:08:43 【问题描述】:

我有一个案例,我有一个gridview/listbox/任何类型的项目控件,并且绑定到该控件的项目数量很大(很容易大约 5000+ 标记)。

这些项目中的每一个都需要具有从各种 Web 服务加载的各种属性。显然,使用 Web 服务一次性处理这么多的元素是不可能的。

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

我以前见过它,但我不记得具体在哪里。在这种情况下,很多股票报价都在绑定到网格视图的集合中,但它们的属性(价格等)在第一次显示(通过滚动到它们各自的位置)之前是空的。

希望这是有道理的。

关于如何实现它的任何想法?

【问题讨论】:

【参考方案1】:

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

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);
        
    

【讨论】:

【参考方案2】:

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

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

这将为用户提供良好的体验,并且大部分棘手的工作将通过虚拟化列表完成(在 WPF 中,这是ListBoxListViewDataGrid...)。希望这会有所帮助。

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();
     
  

  ...

附加逻辑 作为一个想法,您还可以创建一个外部异步加载线程,该线程在后台加载所有数据,但有一个应该以更高优先级加载的项目列表。这个概念和上面的例子是一样的,但是TriggerLoadIfNecessary-方法不是从你的ViewModel-item加载数据,而是只在高优先级列表中添加这个项目,以便首先加载潜在的可见元素。哪个版本更适合的问题取决于列表的使用。如果用户可能使用完整列表并且没有快速导航离开,则此扩展版本更好。否则原版可能会更好。

【讨论】:

我明白你在说什么。这是处理服务调用和所有事情的好方法。但是,我对如何处理第一次呈现元素的事件很感兴趣。换句话说,我想知道如何处理正在显示的元素。我以前看过它……但我不确定。有什么活动吗? 不确定这是否是您的意思,但是通过上述在虚拟化列表(DataBound)中使用 ViewModel-items 的解决方案,您的 line-items 的属性只会在用户滚动时调用到相应的行。这意味着我的示例中的 TriggerLoadIfNecessary 方法仅在显示该行之前调用。对于不在视图中的所有行,将不会调用任何属性,因此不会触发数据加载。这就是为什么我在我的帖子中写道“大部分棘手的工作将通过虚拟化列表完成”。我已经扩展了我的例子...... 我现在明白了,谢谢。另一个不错的解决方案是使用 Loaded 事件处理程序,正如我稍后发现的那样。

以上是关于不可见元素的延迟加载的主要内容,如果未能解决你的问题,请参考以下文章

延迟加载 - UITableview 可见单元格不显示图像

具有延迟加载和下载图像的 ListView [重复]

React 中的延迟加载 util 函数

延迟加载 Flash 对象

jQuery延迟加载(懒加载)插件 – jquery.lazyload.js

动态设置 iframe 的 src 以延迟加载