考虑以下示例XAML文件,该文件显示Facebook的前1000人,以markz作为第4人开始。请注意,这只是一个示例。无论你如何构建任何具有1000个元素的窗口,都是一个很好的演示。
<Window x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
背后的代码:
public partial class MainWindow : Window
{
public MainWindow() {
InitializeComponent();
string[] urls = new string[1000];
for (int i = 0; i < 1000; ++i) {
urls[i] = "http://graph.facebook.com/" + i + "/picture";
}
this.DataContext = urls;
}
}
在非常合理的桌面和高速连接上,程序非常慢。尝试使用ScrollBar滚动...说到中间,需要30秒。点击“主页”和“结束”键将花费大量时间。
这不是第一次只能获取图像到缓存的问题。来回看看已经呈现的图片有点快,但通常很慢。似乎没有任何东西存储在缓存中,关闭应用程序并重新启动它,一切都很慢。
等效的HTML代码快速消失。有些慢,第一次,但一切都很快。
发生了什么事?元素是否使用任何缓存?列表是否对当前未显示的图像进行预取?无论如何要告诉它吗?真的,我唯一的解决方案是自己管理Bitmap对象,以及缓存和预取逻辑吗?如果是这样,我可以合并以前的任何工作?
编辑(摘要):
答案 0 :(得分:6)
ListBoxes
默认情况下虚拟化项目,因此如果向下滚动,则会动态创建项目。首先,它需要下载图像,然后对其进行解码。如果您滚动浏览了所有图像,它们可能会被缓存,但ListBox
仍将重新创建Image
控件,因此每次都需要再次解码图像。
您可以通过将false
设置为ListBox
Recycling
来关闭虚拟化,然后立即加载所有内容,或者您可以将VirtualizingStackPanel.IsVirtualizing
attached property更改为{{ 1}},然后Images
(并包含ListBoxItems
)一旦创建就不会被丢弃。
答案 1 :(得分:3)
另一种方法是添加自己的图像缓存,以便只下载一次图像。
使用我的示例,您可以将其放在构造函数中
this.DataContext = new ViewModel();
以下类将存储url,然后在首次访问Image属性时下载图像。
public class CachingImage
{
private readonly Uri _uri;
public CachingImage(string uriString)
{
_uri = new Uri(uriString, UriKind.RelativeOrAbsolute);
}
private BitmapImage _image;
public ImageSource Image
{
get
{
if (_image == null)
{
_image = new BitmapImage(_uri);
_image.DownloadCompleted += (sender, args) => ((BitmapImage)sender).Freeze();
}
return _image;
}
}
}
这是视图模型
public class ViewModel
{
public ViewModel()
{
Images = Enumerable.Range(1, 1000).Select(i => new CachingImage("http://graph.facebook.com/" + i + "/picture"));
}
public IEnumerable<CachingImage> Images { get; private set; }
...
当然你需要稍微改变你的xaml
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Image}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>