不可见元素的延迟加载
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 中,这是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();
...
附加逻辑
作为一个想法,您还可以创建一个外部异步加载线程,该线程在后台加载所有数据,但有一个应该以更高优先级加载的项目列表。这个概念和上面的例子是一样的,但是TriggerLoadIfNecessary
-方法不是从你的ViewModel-item加载数据,而是只在高优先级列表中添加这个项目,以便首先加载潜在的可见元素。哪个版本更适合的问题取决于列表的使用。如果用户可能使用完整列表并且没有快速导航离开,则此扩展版本更好。否则原版可能会更好。
【讨论】:
我明白你在说什么。这是处理服务调用和所有事情的好方法。但是,我对如何处理第一次呈现元素的事件很感兴趣。换句话说,我想知道如何处理正在显示的元素。我以前看过它……但我不确定。有什么活动吗? 不确定这是否是您的意思,但是通过上述在虚拟化列表(DataBound)中使用 ViewModel-items 的解决方案,您的 line-items 的属性只会在用户滚动时调用到相应的行。这意味着我的示例中的 TriggerLoadIfNecessary 方法仅在显示该行之前调用。对于不在视图中的所有行,将不会调用任何属性,因此不会触发数据加载。这就是为什么我在我的帖子中写道“大部分棘手的工作将通过虚拟化列表完成”。我已经扩展了我的例子...... 我现在明白了,谢谢。另一个不错的解决方案是使用 Loaded 事件处理程序,正如我稍后发现的那样。以上是关于不可见元素的延迟加载的主要内容,如果未能解决你的问题,请参考以下文章