ListView
VirtualizingStackPanel
ItemsPanelTemplate
为 <Image Source="{Binding Filename, Converter={StaticResource ImageConverter}}" />
并且它拥有数百个缩略图(我知道它通常是糟糕的用户体验,但这是人们使用此应用程序的方式)
为了最大限度地减少内存使用量,我只保留模型中缩略图的路径,并在使用iValueConverter滚动到视图时将其转换为图像。
性能非常糟糕,使得用户界面无法响应,甚至在上下滚动后应用程序会冻结。
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
string FileName = value as string;
var file = Windows.Storage.StorageFile.GetFileFromPathAsync(FileName).AsTask().Result;
var stream = file.OpenReadAsync().AsTask().Result;
var bitmapImage = new BitmapImage();
bitmapImage.SetSource(stream);
return bitmapImage;
}
和转换器
IsAsync=true
有更好的方法吗?用户可以滚动一页,跳到中间,或滚动整个列表,所以我认为在模型中预加载BitmapImages是没有意义的。如果我这样做,滚动是顺利的,但应用程序内存占用量为1GB +,这是不可接受的。
WPF曾经有GRANT ALL ON insertarUsuario TO PUBLIC
用于绑定,但UWP无法使用。
答案 0 :(得分:3)
问题是您在UI线程上执行了多次繁重的操作:
var file = Windows.Storage.StorageFile.GetFileFromPathAsync(FileName).AsTask().Result;
var stream = file.OpenReadAsync().AsTask().Result;
var bitmapImage = new BitmapImage();
bitmapImage.SetSource(stream);
如果您认为自己将快速滚动列表时出现的每个缩略图相乘,显然您会看到性能问题。该应用冻结了您可能因为您使用Task.Result
阻止异步方法,这很容易导致deadlock。尽管SetSource
方法不是异步,但它也很昂贵。
我认为您正在阅读信息流并从permissions concerns拨打SetSource
。如果您可以直接将Image.Source
绑定到路径字符串,那么这是目前最好的方法。
否则,您需要创建自定义行为以异步设置源。这是一个粗略的轮廓:
public static class ImageSourceStreamBehavior
{
private static CancellationTokenSource GetCTS(DependencyObject obj)
{
return (CancellationTokenSource)obj.GetValue(CTSProperty);
}
private static void SetCTS(DependencyObject obj, CancellationTokenSource value)
{
obj.SetValue(CTSProperty, value);
}
public static readonly DependencyProperty CTSProperty =
DependencyProperty.RegisterAttached("CTS", typeof(CancellationTokenSource), typeof(ImageSourceStreamBehavior), new PropertyMetadata(null));
public static string GetSource(Image obj)
{
return (string)obj.GetValue(SourceProperty);
}
public static void SetSource(Image obj, string value)
{
obj.SetValue(SourceProperty, value);
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached("Source", typeof(string), typeof(ImageSourceStreamBehavior), new PropertyMetadata(null,
(o, e) => OnSourceChanged((Image)o, (string)e.NewValue)));
private static void OnSourceChanged(Image image, string newValue)
{
GetCTS(image)?.Cancel();
var cts = new CancellationTokenSource();
SetCTS(image, cts);
SetSourceAsync(cts.Token, image, newValue);
}
private static async Task SetSourceAsync(CancellationToken ct, Image image, string fileName)
{
var file = await StorageFile.GetFileFromPathAsync(fileName).AsTask(ct);
if (ct.IsCancellationRequested) { return; }
var stream = await file.OpenReadAsync().AsTask(ct);
if (ct.IsCancellationRequested) { return; }
if (image.Source == null)
{
image.Source = new BitmapImage();
}
await ((BitmapImage)image.Source).SetSourceAsync(stream);
}
}
由于列表中的项目被回收,因此如果图像在完全显示之前滚动出视图,则会取消应用前一图像。此外,您不再阻止UI线程。
您可以按如下方式使用它:
<Image ImageSourceStreamBehavior.Source="{Binding Filename}"/>
另外,正如Justin XL指出的那样,除非你有令人信服的理由,否则你应该使用ItemsStackpanel
。