加载大量图像以显示在WrapPanel中 [英] Loading a large amount of images to be displayed in a WrapPanel

查看:117
本文介绍了加载大量图像以显示在WrapPanel中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用实体框架代码



我有一个电影,如下所示:

  public class Movie 
{
public byte [] Thumbnail {get;组; }
public int MovieId {get;组; }
}

电影的集合如此:

  public class NorthwindContext:DbContext 
{
public DbSet< Movie>电影{get;组;
}

我有一个 MovieViewModel / p>

  public class MovieViewModel 
{
private readonly Movie _movie;

public MovieViewModel(Movie movie)
{
_movieModel = movieModel;
}

public byte [] Thumbnail {get {return _movie.Thumbnail; }}
}

当我的应用启动时:

  public ObservableCollection< MovieViewModel> MovieVms = 
new ObservableCollection< MovieViewModel>();

foreach(MyDbContext.Movies中的var movie)
MovieVms.Add(new MovieViewModel(movie));

我有4000部电影。此过程需要 25秒。有没有更好/更快的方式来做到这一点?



我的主页使用像这样的缩略图,但要清楚这个加载时间是在任何UI相关之前发生的:

  MyView = new ListCollectionView(MovieVms); 

< ListBox ItemsSource ={Binding MyView}/>

此外,我的内存使用情况通过屋顶。我该如何加载这些图像?我需要完整的蝙蝠视图模型,以启用排序,过滤,搜索,但我只需要在我的包装面板中可见的项目的缩略图。



编辑---



感谢Dave的一个很好的答案。您可以详细说明使其成为关联(也称为导航属性)

  var thumbnail = new Thumbnail(); 
thumbnail.Data = movie.GetThumbnail();
Globals.DbContext.Thumbnails.Add(thumbnail);
Globals.DbContext.SaveChanges();
movie.ThumbnailId = thumbnail.ThumbnailId;
Globals.DbContext.SaveChanges();

我可以运行该代码没有错误,但我的财产在我的 MovieViewModel

  public new byte [] Thumbnail {get {return _movie.Thumbnail.Data; 

总是有一个null Thumbnail 一旦我的UI访问错误。在 movie.ThumbnailId 上的断点永远不会被打。我必须手动加载关联吗?

解决方案

我认为你本质上是要做几件不同的事情: >


  • 快速加载整个电影列表,以便在用户界面中排序和过滤

  • 显示电影

  • 将内存使用率保持在最低限度

  • 在应用程序后尽可能快地显示用户界面开始



快速加载电影



首先,像@Dave M答复状态下,您需要将缩略图分割成一个独立的实体,以便您可以在实体框架中加载电影列表而不加载缩略图。

  public class Movie 
{
public int Id {get;组; }
public int ThumbnailId {get;组; }
public virtual Thumbnail缩略图{get;组; } //该属性必须声明为虚拟
public string Name {get;组;

//其他属性
}

public class Thumbnail
{
public int Id {get;组; }
public byte [] Image {get;组;


public class MoviesContext:DbContext
{
public MoviesContext(string connectionString)
:base(connectionString)
{
}

public DbSet< Movie>电影{get;组; }
public DbSet< Thumbnail>缩略图{get;组;
}

所以,要加载所有的电影:

  public List< Movie> LoadMovies()
{
//需要从某个地方获取'_connectionString':最好将其传递给类构造函数并存储在一个字段成员
中(var db = new MoviesContext _connectionString))
{
return db.Movies.AsNoTracking()。ToList();
}
}

此时您将有一个 c> c> c> c> c> c> c> c> > null ,因为您没有要求EF加载相关的缩略图实体。此外,如果您尝试访问缩略图属性,您将收到一个例外,因为 MoviesContext 不再具有范围



一旦您有电影实体的列表,您需要将它们转换为ViewModels。我假设你的ViewModels是有效的只读。

 公开封闭的类MovieViewModel 
{
public MovieViewModel(Movie movie)
{
_thumbnailId = movie.ThumbnailId;
Id = movie.Id;
Name = movie.Name;
//复制
之间的其他属性值

readonly int _thumbnailId;

public int Id {get;私人集合}
public string Name {get;私人集合}
//其他电影属性,全部与公共getters和私有设置器

public byte [] Thumbnail {get;私人集合} //稍后会出现这个问题
}

请注意,我们只是将缩略图ID存储在此处,而不是填充缩略图尚未。



分别加载缩略图,并缓存



所以,你加载了电影,但目前还没有加载任何缩略图。您需要的是一种从数据库中为其ID添加单个缩略图实体的方法。我建议将其与某种缓存结合起来,以便一旦加载了缩略图,您可以将其保存在内存中一段时间​​。

  public sealed class ThumbnailCache 
{
public ThumbnailCache(string connectionString)
{
_connectionString = connectionString;
}

只读字符串_connectionString;
只读字典< int,Thumbnail> _cache = new Dictionary< int,Thumbnail>();

public Thumbnail GetThumbnail(int id)
{
缩略图缩略图;

if(!_cache.TryGetValue(id,out thumbnail))
{
//不在缓存中,所以从数据库加载实体
使用(var db = new MoviesContext(_connectionString))
{
thumbnail = db.Thumbnails.AsNoTracking()。Find(id);
}

_cache.Add(id,thumbnail);
}

返回缩略图;
}
}

这显然是一个非常基本的缓存:检索是阻止,没有错误处理,并且如果还没有检索到一段时间,以便保持内存使用不足,那么应该从缓存中删除这些缩略图。



返回到ViewModel,您需要修改构造函数以引用缓存实例,并修改 Thumbnail 属性getter以从缓存中检索缩略图:

 公开封闭的类MovieViewModel 
{
public MovieViewModel(Movie movie,ThumbnailCache thumbnailCache)
{
_thumbnailId = movie.ThumbnailId;
_thumbnailCache = thumbnailCache;
Id = movie.Id;
Name = movie.Name;
//复制
之间的其他属性值

readonly int _thumbnailId;
readonly ThumbnailCache _thumbnailCache;

public int Id {get;私人集合}
public string Name {get;私人集合}
//其他电影属性,全部与公共getter和私有设置器

public BitmapSource Thumbnail
{
get
{
if( _thumbnail == null)
{
byte [] image = _thumbnailCache.GetThumbnail(_thumbnailId).Image;

//转换为BitmapImage进行绑定目的
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = new MemoryStream(image);
bitmapImage.CreateOptions = BitmapCreateOptions.None;
bitmapImage.CacheOption = BitmapCacheOption.Default;
bitmapImage.EndInit();

_thumbnail = bitmapImage;
}

return _thumbnail;
}
}
BitmapSource _thumbnail;
}

现在,只有当访问缩略图属性:如果图像已经在缓存中,它将立即返回,否则将首先从数据库加载,然后存储在缓存中以备将来使用。



绑定性能



绑定 MovieViewModel 的集合的方式对您的看法的控制也将对感知的加载时间产生影响。你想尽可能做的是延迟绑定,直到你的集合被填充。这将比绑定到一个空的集合,然后一次添加一个集合中的项目更快。你可能已经知道了,但是我以为我会提到它,以防万一。



这个MSDN页面(优化性能:数据绑定)有一些有用的提示。



这个Ian Griffiths的一系列博客文章(太多,太快了使用WPF和Async )显示各种绑定策略如何影响绑定列表的加载时间。



仅在视图中加载缩略图



现在最难的一点!当应用程序启动时,我们已经停止加载缩略图,但是我们确实需要在某些时候加载它们。加载它们的最佳时间是当它们在UI中可见时。所以问题成了:如何在UI中检测缩略图的时间?这很大程度上取决于您在视图中使用的控件(UI)。



我将假设您对 MovieViewModel s到某种类型的 ItemsControl ,例如 ListBox ListView 。此外,我假设你有一些 DataTemplate 配置(作为 ListBox / ListView 标记,或位于 MovieViewModel 类型的 ResourceDictionary 某处)。 DataTemplate 的一个非常简单的版本可能如下所示:

  ; DataTemplate DataType ={x:Type ...}> 
< StackPanel>
< Image Source ={Binding Thumbnail}Stretch =FillWidth =100Height =100/>
< TextBlock Text ={Binding Name}/>
< / StackPanel>
< / DataTemplate>

如果您正在使用 ListBox 如果您更改面板,它将使用 WrapPanel ListBox ControlTemplate 包含一个 ScrollViewer ,它提供滚动条并处理任何滚动。在这种情况下,我们可以说,当它出现在 ScrollViewer 的视口中时,缩略图是可见的。因此,我们需要一个自定义的 ScrollViewer 元素,当滚动时,它决定了哪个孩子在视口中可见,并相应地标记它们。标记它们的最好方法是使用附加的布尔属性:这样,我们可以修改 DataTemplate 来触发附加属性值更改并加载该缩略图



以下 ScrollViewer 后裔(对于可怕的名字抱歉)将会做到这一点(注意这个可能是用附加的行为来完成的,而不是必须进行子类化,但是这个答案足够长了)。

  public密封类MyScrollViewer:ScrollViewer 
{
public static readonly DependencyProperty IsInViewportProperty =
DependencyProperty.RegisterAttached(IsInViewport,typeof(bool),typeof(MyScrollViewer));

public static bool GetIsInViewport(UIElement element)
{
return(bool)element.GetValue(IsInViewportProperty);
}

public static void SetIsInViewport(UIElement element,bool value)
{
element.SetValue(IsInViewportProperty,value);
}

protected override void OnScrollChanged(ScrollChangedEventArgs e)
{
base.OnScrollChanged(e);

var panel =内容作为面板;
if(panel == null)
{
return;
}

Rect viewport = new Rect(new Point(0,0),RenderSize);

foreach(UIElement child in panel.Children)
{
if(!child.IsVisible)
{
SetIsInViewport(child,false);
继续;
}

GeneralTransform transform = child.TransformToAncestor(this);
Rect childBounds = transform.TransformBounds(new Rect(new Point(0,0),child.RenderSize));
SetIsInViewport(child,viewport.IntersectsWith(childBounds));
}
}
}

基本上这个 ScrollViewer 假设它是内容是一个面板,并设置附加的 IsInViewport 属性对于位于视口内的小组的孩子,即真实的。对用户可见。现在仍然是修改视图的XAML以将此自定义 ScrollViewer 作为 ListBox 的一部分模板:

 < Window x:Class =...
xmlns =http:// schemas $ m
xmlns:my =clr-namespace :......>

< Window.Resources>
< DataTemplate DataType ={x:键入my:MovieViewModel}>
< StackPanel>
< Image x:Name =ThumbnailStretch =FillWidth =100Height =100/>
< TextBlock Text ={Binding Name}/>
< / StackPanel>
< DataTemplate.Triggers>
< DataTrigger Binding ={Binding Path =(my:MyScrollViewer.IsInViewport),RelativeSource = {RelativeSource AncestorType = {x:Type ListBoxItem}}}
Value =True>
< Setter TargetName =ThumbnailProperty =SourceValue ={Binding Thumbnail}/>
< / DataTrigger>
< /DataTemplate.Triggers>
< / DataTemplate>
< /Window.Resources>

< ListBox ItemsSource ={Binding Movies}>
< ListBox.Template>
< ControlTemplate>
< my:MyScrollViewer Horizo​​ntalScrollBarVisibility =DisabledVerticalScrollBarVisibility =Auto>
< WrapPanel IsItemsHost =True/>
< / my:MyScrollViewer>
< / ControlTemplate>
< /ListBox.Template>
< / ListBox>

< / Window>

这里我们有一个窗口包含一个列表框。我们已将 ListBox 中的 ControlTemplate 更改为包含自定义 ScrollViewer ,里面是 WrapPanel ,它将布局这些项目。在窗口的资源中,我们将使用 DataTemplate ,用于显示每个 MovieViewModel 。这与前面介绍的 DataTemplate 类似,但请注意,我们不再绑定 Image Source 属性在模板的正文中:相反,我们使用基于 IsInViewport 属性的触发器,并设置绑定项目变为可见。此绑定将导致 MovieViewModel 类的缩略图属性getter被调用,这将从缓存或数据库加载缩略图。请注意,绑定是针对父注释 ListBoxItem 的属性,其中注入了 DataTemplate 的标记。 / p>

这种方法的唯一问题是,随着缩略图加载在UI线程上完成,滚动将受到影响。解决这个问题的最简单的方法是修改 MovieViewModel 缩略图属性getter返回一个虚拟缩略图,将调用调度到缓存上一个单独的线程,然后让该线程相应地设置缩略图属性,并提出一个 PropertyChanged 事件,从而确保绑定机制提升了变化。还有其他的解决方案,但是它们会大大提高复杂性:考虑这里提出的只是一个可能的起点。


I am using Entity Framework Code First

I have a Movie like so:

public class Movie
{
        public byte[] Thumbnail { get; set; }
        public int MovieId { get; set; }
}

And a Collection of Movies like so:

public class NorthwindContext : DbContext
{
    public DbSet<Movie> Movies { get; set; }
}

I have a MovieViewModel like so:

public class MovieViewModel
{
        private readonly Movie _movie;

        public MovieViewModel(Movie movie)
        {
            _movieModel = movieModel;
        }

        public byte[] Thumbnail { get { return _movie.Thumbnail; } }
}

When my App starts:

public ObservableCollection<MovieViewModel> MovieVms = 
                      new ObservableCollection<MovieViewModel>();

foreach (var movie in MyDbContext.Movies)
     MovieVms.Add(new MovieViewModel(movie));

I have 4000 movies. This process takes 25 seconds. Is there a better/faster way to do this?

My main page uses the thumbnails like so, but to be clear this loading time happens before anything UI related:

MyView = new ListCollectionView(MovieVms);

<ListBox ItemsSource="{Binding MyView}" />

Also my memory usage goes through the roof. How should I be loading these images? I need a full collection of view models off the bat to enable sorting, filtering, searching, but I only need the thumbnails of the items visible in my wrap panel.

EDIT---

Thanks Dave for a great answer. Can you elaborate on "make it an association (aka navigation property)"

var thumbnail = new Thumbnail();
thumbnail.Data = movie.GetThumbnail();
Globals.DbContext.Thumbnails.Add(thumbnail);
Globals.DbContext.SaveChanges();
movie.ThumbnailId = thumbnail.ThumbnailId;
Globals.DbContext.SaveChanges();

I can run that code with no errors, but my property in my MovieViewModel

public new byte[] Thumbnail { get { return _movie.Thumbnail.Data; } }

always has a null Thumbnail and errors as soon as my UI accesses it. A breakpoint on movie.ThumbnailId is never hit. Do I have to load the association manually?

解决方案

I think you are essentially asking how to do several different things:

  • Load the entire list of movies quickly, to allow for sorting and filtering in the UI
  • Display the movie thumbnails in the UI but only when they are scrolled into view
  • Keep memory usage to a minimum
  • Display the UI as quickly as possible after the application starts

Load the movies quickly

First off, as @Dave M's answer states, you need to split the thumbnail into a separate entity so that you can ask Entity Framework to load the list of movies without also loading the thumbnails.

public class Movie
{
    public int Id { get; set; }
    public int ThumbnailId { get; set; }
    public virtual Thumbnail Thumbnail { get; set; }  // This property must be declared virtual
    public string Name { get; set; }

    // other properties
}

public class Thumbnail
{
    public int Id { get; set; }
    public byte[] Image { get; set; }
}

public class MoviesContext : DbContext
{
    public MoviesContext(string connectionString)
        : base(connectionString)
    {
    }

    public DbSet<Movie> Movies { get; set; }
    public DbSet<Thumbnail> Thumbnails { get; set; }
}

So, to load all of the movies:

public List<Movie> LoadMovies()
{
    // Need to get '_connectionString' from somewhere: probably best to pass it into the class constructor and store in a field member
    using (var db = new MoviesContext(_connectionString))
    {
        return db.Movies.AsNoTracking().ToList();
    }
}

At this point you will have a list of Movie entities where the ThumbnailId property is populated but the Thumbnail property will be null as you have not asked EF to load the related Thumbnail entities. Also, should you try to access the Thumbnail property later you will get an exception as the MoviesContext is no longer in scope.

Once you have a list of Movie entities, you need to convert them into ViewModels. I'm assuming here that your ViewModels are effectively read-only.

public sealed class MovieViewModel
{
    public MovieViewModel(Movie movie)
    {
        _thumbnailId = movie.ThumbnailId;
        Id = movie.Id;
        Name = movie.Name;
        // copy other property values across
    }

    readonly int _thumbnailId;

    public int Id { get; private set; }
    public string Name { get; private set; }
    // other movie properties, all with public getters and private setters

    public byte[] Thumbnail { get; private set; }  // Will flesh this out later!
}

Note that we're just storing the thumbnail ID here, and not populating the Thumbnail yet. I'll come to that in a bit.

Load thumbnails separately, and cache them

So, you've loaded the movies, but at the moment you haven't loaded any thumbnails. What you need is a method that will load a single Thumbnail entity from the database given its ID. I would suggest combining this with a cache of some sort, so that once you've loaded a thumbnail image you keep it in memory for a while.

public sealed class ThumbnailCache
{
    public ThumbnailCache(string connectionString)
    {
        _connectionString = connectionString;
    }

    readonly string _connectionString;
    readonly Dictionary<int, Thumbnail> _cache = new Dictionary<int, Thumbnail>();

    public Thumbnail GetThumbnail(int id)
    {
        Thumbnail thumbnail;

        if (!_cache.TryGetValue(id, out thumbnail))
        {
            // Not in the cache, so load entity from database
            using (var db = new MoviesContext(_connectionString))
            {
                thumbnail = db.Thumbnails.AsNoTracking().Find(id);
            }

            _cache.Add(id, thumbnail);
        }

        return thumbnail;
    }
}

This is obviously a very basic cache: the retrieval is blocking, there is no error handling, and the thumbnails should really be removed from the cache if they haven't been retrieved for a while in order to keep memory usage down.

Going back to the ViewModel, you need to modify the constructor to take a reference to a cache instance, and also modify the Thumbnail property getter to retrieve the thumbnail from the cache:

public sealed class MovieViewModel
{
    public MovieViewModel(Movie movie, ThumbnailCache thumbnailCache)
    {
        _thumbnailId = movie.ThumbnailId;
        _thumbnailCache = thumbnailCache;
        Id = movie.Id;
        Name = movie.Name;
        // copy other property values across
    }

    readonly int _thumbnailId;
    readonly ThumbnailCache _thumbnailCache;

    public int Id { get; private set; }
    public string Name { get; private set; }
    // other movie properties, all with public getters and private setters

    public BitmapSource Thumbnail
    {
        get
        {
            if (_thumbnail == null)
            {
                byte[] image = _thumbnailCache.GetThumbnail(_thumbnailId).Image;

                // Convert to BitmapImage for binding purposes
                var bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = new MemoryStream(image);
                bitmapImage.CreateOptions = BitmapCreateOptions.None;
                bitmapImage.CacheOption = BitmapCacheOption.Default;
                bitmapImage.EndInit();

                _thumbnail = bitmapImage;
            }

            return _thumbnail;
        }
    }
    BitmapSource _thumbnail;
}

Now the thumnail images will only be loaded when the Thumbnail property is accessed: if the image was already in the cache, it will be returned immediately, otherwise it will be loaded from the database first and then stored in the cache for future use.

Binding performance

The way that you bind your collection of MovieViewModels to the control in your view will have an impact on perceived loading time as well. What you want to do whenever possible is to delay the binding until your collection has been populated. This will be quicker than binding to an empty collection and then adding items to the collection one at a time. You may already know this but I thought I'd mention it just in case.

This MSDN page (Optimizing Performance: Data Binding) has some useful tips.

This awesome series of blog posts by Ian Griffiths (Too Much, Too Fast with WPF and Async) shows how various binding strategies can affect the load times of a bound list.

Only loading thumbnails when in view

Now for the most difficult bit! We've stopped the thumbnails from loading when the application starts, but we do need to load them at some point. The best time to load them is when they are visible in the UI. So the question becomes: how do I detect when the thumbnail is visible in the UI? This largely depends on the controls you are using in your view (the UI).

I'll assume that you are binding your collection of MovieViewModels to an ItemsControl of some type, such as a ListBox or ListView. Furthermore, I'll assume that you have some kind of DataTemplate configured (either as part of the ListBox/ListView markup, or in a ResourceDictionary somewhere) that is mapped to the MovieViewModel type. A very simple version of that DataTemplate might look like this:

<DataTemplate DataType="{x:Type ...}">
    <StackPanel>
        <Image Source="{Binding Thumbnail}" Stretch="Fill" Width="100" Height="100" />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

If you are using a ListBox, even if you change the panel it uses to something like a WrapPanel, the ListBox's ControlTemplate contains a ScrollViewer, which provides the scroll bars and handles any scrolling. In this case then, we can say that a thumbnail is visible when it appears within the ScrollViewer's viewport. Therefore, we need a custom ScrollViewer element that, when scrolled, determines which of its "children" are visible in the viewport, and flags them accordingly. The best way of flagging them is to use an attached Boolean property: in this way, we can modify the DataTemplate to trigger on the attached property value changing and load the thumbnail at that point.

The following ScrollViewer descendant (sorry for the terrible name!) will do just that (note that this could probably be done with an attached behaviour instead of having to subclass, but this answer is long enough as it is).

public sealed class MyScrollViewer : ScrollViewer
{
    public static readonly DependencyProperty IsInViewportProperty =
        DependencyProperty.RegisterAttached("IsInViewport", typeof(bool), typeof(MyScrollViewer));

    public static bool GetIsInViewport(UIElement element)
    {
        return (bool) element.GetValue(IsInViewportProperty);
    }

    public static void SetIsInViewport(UIElement element, bool value)
    {
        element.SetValue(IsInViewportProperty, value);
    }

    protected override void OnScrollChanged(ScrollChangedEventArgs e)
    {
        base.OnScrollChanged(e);

        var panel = Content as Panel;
        if (panel == null)
        {
            return;
        }

        Rect viewport = new Rect(new Point(0, 0), RenderSize);

        foreach (UIElement child in panel.Children)
        {
            if (!child.IsVisible)
            {
                SetIsInViewport(child, false);
                continue;
            }

            GeneralTransform transform = child.TransformToAncestor(this);
            Rect childBounds = transform.TransformBounds(new Rect(new Point(0, 0), child.RenderSize));
            SetIsInViewport(child, viewport.IntersectsWith(childBounds));
        }
    }
}

Basically this ScrollViewer assumes that it's Content is a panel, and sets the attached IsInViewport property to true for those children of the panel that lie within the viewport, ie. are visible to the user. All that remains now is to modify the XAML for the view to include this custom ScrollViewer as part of the ListBox's template:

<Window x:Class="..."
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:...">

    <Window.Resources>
        <DataTemplate DataType="{x:Type my:MovieViewModel}">
            <StackPanel>
                <Image x:Name="Thumbnail" Stretch="Fill" Width="100" Height="100" />
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=(my:MyScrollViewer.IsInViewport), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
                             Value="True">
                    <Setter TargetName="Thumbnail" Property="Source" Value="{Binding Thumbnail}" />
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>

    <ListBox ItemsSource="{Binding Movies}">
        <ListBox.Template>
            <ControlTemplate>
                <my:MyScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
                    <WrapPanel IsItemsHost="True" />
                </my:MyScrollViewer>
            </ControlTemplate>
        </ListBox.Template>
    </ListBox>

</Window>

Here we have a Window containing a single ListBox. We've changed the ControlTemplate of the ListBox to include the custom ScrollViewer, and inside that is the WrapPanel that will layout the items. In the window's resources we have the DataTemplate that will be used to display each MovieViewModel. This is similar to the DataTemplate introduced earlier, but note that we are no longer binding the Image's Source property in the body of the template: instead, we use a trigger based on the IsInViewport property, and set the binding when the item becomes 'visible'. This binding will cause the MovieViewModel class's Thumbnail property getter to be called, which will load the thumbnail image either from the cache or the database. Note that the binding is to the property on the parent ListBoxItem, into which the markup for the DataTemplate is injected.

The only problem with this approach is that, as the thumbnail loading is done on the UI thread, scrolling will be affected. The easiest way to fix this would be to modify the MovieViewModel Thumbnail property getter to return a "dummy" thumbnail, schedule the call to the cache on a separate thread, then get that thread to set the Thumbnail property accordingly and raise a PropertyChanged event, thus ensuring the binding mechanism picks-up the change. There are other solutions but they would raise the complexity significantly: consider what is presented here as just a possible starting point.

这篇关于加载大量图像以显示在WrapPanel中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆