在运行时将大图像显示在视图中
Bringing large image into view at runtime
问题来了。
我有一个视图,它应该显示一个图像和一些控件。
用户添加新图像,更改一些选项并单击 "finish"。
图片很大而且非常大 (400-1500 MB Tiff)
用户应该看到图像的预览,但如果它加载 10-15 秒甚至更长时间也没关系,他这次有工作。
图像通过 MVVM
模式绑定,如简单字符串(文件将始终在本地文件夹中)
<Image Name="ImagePreview" Source="{Binding SFilePathForPreview,
FallbackValue={StaticResource DefaultImage},
TargetNullValue={StaticResource DefaultImage}}"
HorizontalAlignment="Center" Width="200" Height="200"
VerticalAlignment="Center" />
问题是当用户尝试为加载时间添加文件时全部挂起。
我知道这种情况应该通过多线程解决 - 但不知道如何实现。
我尝试像这样从不同线程的视图中更新图像:
Thread newThread = new Thread(LazyLoad);
newThread.Name = "LazyLoad";
newThread.Start(SFilePathForPreview);
public void LazyLoad(object SFilePath)
{
try
{
string path = (string)SFilePath;
BitmapImage t_source = new BitmapImage();
t_source.BeginInit();
t_source.UriSource = new Uri(path);
t_source.DecodePixelWidth = 200;
t_source.EndInit();
t_source.Freeze();
this.Dispatcher.Invoke(new Action(delegate
{
ImagePreview.Source = t_source;
}));
}
catch
{
//...
}
}
但无论如何
ImagePreview.Source = t_source;
在图像完全加载之前一切都挂起。
有没有办法在后台加载预览并显示它而不会出现那些可怕的挂起?
正如您已经提到的,您在加载图像时阻塞了 UI 线程。您可以使用 WriteableBitmap class 实例作为图像的来源。这将使您可以在后台线程或异步任务上加载图像。这是关于这个问题的快速指南(不是我的)。
https://www.i-programmer.info/programming/wpf-workings/527-writeablebitmap.html
异步加载图像的最简单方法可能是通过异步绑定。您根本不需要处理线程或任务。
<Image Source="{Binding Image, IsAsync=True}"/>
可能的视图模型如下所示,您必须确保可以从后台线程调用 Image
属性 getter。
public class ViewModel : ViewModelBase
{
private string imagePath;
private BitmapImage image;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
image = null;
OnPropertyChanged(nameof(ImagePath));
OnPropertyChanged(nameof(Image));
}
}
public BitmapImage Image
{
get
{
lock (this)
{
if (image == null &&
!string.IsNullOrEmpty(imagePath) &&
File.Exists(imagePath))
{
using (var stream = File.OpenRead(imagePath))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.DecodePixelWidth = 200;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
}
}
}
return image;
}
}
}
另一种选择是使用 priortybinding,将最高优先级绑定到完整图像,将较低优先级绑定到加载速度更快的预览图像。 MS 在此处记录了优先级绑定:
https://docs.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-prioritybinding
问题来了。
我有一个视图,它应该显示一个图像和一些控件。
用户添加新图像,更改一些选项并单击 "finish"。
图片很大而且非常大 (400-1500 MB Tiff)
用户应该看到图像的预览,但如果它加载 10-15 秒甚至更长时间也没关系,他这次有工作。
图像通过 MVVM
模式绑定,如简单字符串(文件将始终在本地文件夹中)
<Image Name="ImagePreview" Source="{Binding SFilePathForPreview,
FallbackValue={StaticResource DefaultImage},
TargetNullValue={StaticResource DefaultImage}}"
HorizontalAlignment="Center" Width="200" Height="200"
VerticalAlignment="Center" />
问题是当用户尝试为加载时间添加文件时全部挂起。 我知道这种情况应该通过多线程解决 - 但不知道如何实现。
我尝试像这样从不同线程的视图中更新图像:
Thread newThread = new Thread(LazyLoad);
newThread.Name = "LazyLoad";
newThread.Start(SFilePathForPreview);
public void LazyLoad(object SFilePath)
{
try
{
string path = (string)SFilePath;
BitmapImage t_source = new BitmapImage();
t_source.BeginInit();
t_source.UriSource = new Uri(path);
t_source.DecodePixelWidth = 200;
t_source.EndInit();
t_source.Freeze();
this.Dispatcher.Invoke(new Action(delegate
{
ImagePreview.Source = t_source;
}));
}
catch
{
//...
}
}
但无论如何
ImagePreview.Source = t_source;
在图像完全加载之前一切都挂起。
有没有办法在后台加载预览并显示它而不会出现那些可怕的挂起?
正如您已经提到的,您在加载图像时阻塞了 UI 线程。您可以使用 WriteableBitmap class 实例作为图像的来源。这将使您可以在后台线程或异步任务上加载图像。这是关于这个问题的快速指南(不是我的)。
https://www.i-programmer.info/programming/wpf-workings/527-writeablebitmap.html
异步加载图像的最简单方法可能是通过异步绑定。您根本不需要处理线程或任务。
<Image Source="{Binding Image, IsAsync=True}"/>
可能的视图模型如下所示,您必须确保可以从后台线程调用 Image
属性 getter。
public class ViewModel : ViewModelBase
{
private string imagePath;
private BitmapImage image;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
image = null;
OnPropertyChanged(nameof(ImagePath));
OnPropertyChanged(nameof(Image));
}
}
public BitmapImage Image
{
get
{
lock (this)
{
if (image == null &&
!string.IsNullOrEmpty(imagePath) &&
File.Exists(imagePath))
{
using (var stream = File.OpenRead(imagePath))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.DecodePixelWidth = 200;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
}
}
}
return image;
}
}
}
另一种选择是使用 priortybinding,将最高优先级绑定到完整图像,将较低优先级绑定到加载速度更快的预览图像。 MS 在此处记录了优先级绑定:
https://docs.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-prioritybinding