如何异步加载和显示图像

时间:2017-03-30 15:55:11

标签: c# wpf multithreading xaml

我是WPF的新手,但我已经做了很长时间的C#并且我正在开发一个简单的窗口(Windows桌面),它应该可视化目录中的所有照片。应用程序还应该知道像ISO,光圈等EXIF数据,我使用DLL。

我定义了一个Photo类:

public class Photo {

    public string FileName { get; set; }
    public int ISO { get; set; }
    ...
}

我希望在运行时存储在List<Photo>中。

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

public partial class PhotoItem : UserControl {
    ...
    public Photo Photo { get; set; }
    ...
}

Photo属性中,PhotoItem知道在哪里查找图像以及要显示的ISO等。

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

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

另一方面,我也考虑在PhotoItem内部实现类似'Refresh'功能的东西,但这会重新加载图像并需要很长时间(这可能是我最喜欢的WinForms方式做吧,哈哈)。

有人能告诉我如何实现这个目标吗?

提前致谢!

1 个答案:

答案 0 :(得分:4)

让我们看看没有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";
    }
}

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

public class ViewModel
{
    public ObservableCollection<Photo> Photos { get; }
        = new ObservableCollection<Photo>();
}

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

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>

最后一部分是ListBox的声明,其中DataTemplate可视化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>

现在,您可以在MainWindow的异步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();
    }
}