如何异步加载和显示图像

Fab*_*abi 1 c# wpf xaml multithreading

我是 WPF 新手,但我已经使用 C# 有一段时间了,目前正在开发一个简单的窗口(Windows 桌面),该窗口应该可视化目录中的所有照片。应用程序还应该了解 EXIF 数据,例如 ISO、光圈等,我为此使用了 DLL。

我定义了一个Photo类:

public class Photo {

    public string FileName { get; set; }
    public int ISO { get; set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

我想List<Photo>在运行时存储在 a 中。

然后我声明了一个PhotoItem(XAML 用户控件),其中包含一个图像控件和一个 TextBlock。对于每一个Photo创建的对象,都会有一个PhotoItem创建对象将相应的Photo属性保存为:

public partial class PhotoItem : UserControl {
    ...
    public Photo Photo { get; set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

从这个Photo属性PhotoItem中,知道在哪里寻找图像以及要显示什么 ISO 等。

现在我的问题。因为如果用户选择目录,加载图像本身以及元数据会花费太长时间,所以我想首先将所有 s 添加PhotoItem到窗口(仍然为空),然后运行元数据查找和图像缩略图加载对于他们每个人来说。当然,如果这些操作不阻塞 UI 线程那就最好了,因此我目前使用一个Task用于收集元数据,一个用于收集缩略图。

如果图像的元数据现在可用,我将如何更新 PhotoItems?基本上,如何才能拥有一个集中位置来存储所有数据、任务可以向该位置提供更新以及 UI 线程可以从中构建信息。我对 XAML/WPF 中的绑定了解一些,但是Photo.ISO如果尚未收集元数据,则将 TextBlock 的文本绑定到变量将始终显示零。在这种情况下,我想隐藏PhotoItem.

另一方面,我也考虑过在 中实现类似“刷新”功能的功能PhotoItem,但这会重新加载图像并且需要很长时间(这可能是我最喜欢的 WinForms 方法,哈哈)。

谁能告诉我如何实现这一点?

提前致谢!

Cle*_*ens 7

让我们看一个没有 UserControl 的基本示例。

第一步是创建视图模型以启用数据绑定。您可以让 Photo 类实现接口,INotifyPropertyChanged以便在属性值更改时更新绑定。

下面的类还声明了Image一个保存派生对象的属性ImageSource,该对象是异步加载的。

public class Photo : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this,
            new PropertyChangedEventArgs(propertyName));
    }

    public string FileName { get; set; }

    private string iso = string.Empty;
    public string ISO
    {
        get { return iso; }
        set
        {
            iso = value;
            NotifyPropertyChanged(nameof(ISO));
        }
    }

    private ImageSource image;
    public ImageSource Image
    {
        get { return image; }
        set
        {
            image = value;
            NotifyPropertyChanged(nameof(Image));
        }
    }

    public async Task Load()
    {
        Image = await Task.Run(() =>
        {
            using (var fileStream = new FileStream(
                FileName, FileMode.Open, FileAccess.Read))
            {
                return BitmapFrame.Create(
                    fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
            }
        });

        ISO = "1600";
    }
}
Run Code Online (Sandbox Code Playgroud)

视图模型的第二部分是一个包含Photo实例集合的类:

public class ViewModel
{
    public ObservableCollection<Photo> Photos { get; }
        = new ObservableCollection<Photo>();
}
Run Code Online (Sandbox Code Playgroud)

DataContext对于典型的数据绑定场景,您可以在代码或 XAML 中将此类的实例分配给您的窗口:

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
Run Code Online (Sandbox Code Playgroud)

DataTemplate最后一部分是带有可视化 a的 ListBox 的声明Photo

<ListBox ItemsSource="{Binding Photos}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding Image}" Width="100" Height="100"/>
                <StackPanel>
                    <TextBlock Text="{Binding ISO, StringFormat=ISO: {0}}"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
Run Code Online (Sandbox Code Playgroud)

Photos现在,您可以在主窗口的异步事件处理程序中填充该集合Loaded,如下所示:

private async void Window_Loaded(object sender, RoutedEventArgs e)
{
    var vm = (ViewModel)DataContext;

    foreach (var file in Directory.EnumerateFiles(...))
    {
        vm.Photos.Add(new Photo { FileName = file });
    }

    foreach (var photo in vm.Photos)
    {
        await photo.Load();
    }
}
Run Code Online (Sandbox Code Playgroud)