2017-10-12 124 views
1

我试图异步加载图像。异步加载C#中的BitmapImage

主窗口代码

public partial class MainWindow : Window 
{ 
    private Data data = new Data(); 
    public MainWindow() 
    { 
     InitializeComponent(); 
     this.DataContext = data; 
    } 
    private async void button_Click(object sender, RoutedEventArgs e) 
    { 
     data.Image = await Data.GetNewImageAsync(); 
    } 
} 

数据类

public class Data : INotifyPropertyChanged 
{ 
    private BitmapImage _Image = new BitmapImage(); 
    public BitmapImage Image { get { return _Image; } set { _Image = value; OnPropertyChanged("Image"); } } 

    public static BitmapImage GetNewImage() 
    { 
     return new BitmapImage(new Uri("http://www.diseno-art.com/news_content/wp-content/uploads/2012/09/2013-Jaguar-F-Type-1.jpg")); 
    } 

    public async static Task<BitmapImage> GetNewImageAsync() 
    { 
     return await Task.Run(() => GetNewImage()); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    public void OnPropertyChanged(string propertyName) 
    { 
     if (PropertyChanged != null) 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

WPF代码

<Button Name="button" Click="button_Click">Image</Button> 
<Image Grid.Row="1" Source="{Binding Path=Image, UpdateSourceTrigger=PropertyChanged}"></Image> 

问题

我得到异常:

System.ArgumentException: “必须在相同的线程 作为DependencyObject的创建DependencySource”

...在此行中: PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

但是,如果我改变的BitmapImage字符串此代码工作正常。

我在做什么错?

+0

作为说明,在图像绑定上设置'UpdateSourceTrigger = PropertyChanged'是没有意义的。它只对TwoWay和OneWayToSource绑定产生影响。 – Clemens

回答

5

在后台线程中创建BitmapImage时,必须确保它在UI线程中使用之前被冻结。

你将不得不自己加载它是这样的:

public static async Task<BitmapImage> GetNewImageAsync(Uri uri) 
{ 
    BitmapImage bitmap = null; 
    var httpClient = new HttpClient(); 

    using (var response = await httpClient.GetAsync(uri)) 
    { 
     if (response.IsSuccessStatusCode) 
     { 
      using (var stream = new MemoryStream()) 
      { 
       await response.Content.CopyToAsync(stream); 
       stream.Seek(0, SeekOrigin.Begin); 

       bitmap = new BitmapImage(); 
       bitmap.BeginInit(); 
       bitmap.CacheOption = BitmapCacheOption.OnLoad; 
       bitmap.StreamSource = stream; 
       bitmap.EndInit(); 
       bitmap.Freeze(); 
      } 
     } 
    } 

    return bitmap; 
} 

或更短与BitmapFrame.Create,它返回一个已冻结的BitmapSource:

public static async Task<BitmapSource> GetNewImageAsync(Uri uri) 
{ 
    BitmapSource bitmap = null; 
    var httpClient = new HttpClient(); 

    using (var response = await httpClient.GetAsync(uri)) 
    { 
     if (response.IsSuccessStatusCode) 
     { 
      using (var stream = new MemoryStream()) 
      { 
       await response.Content.CopyToAsync(stream); 
       stream.Seek(0, SeekOrigin.Begin); 

       bitmap = BitmapFrame.Create(
        stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); 
      } 
     } 
    } 

    return bitmap; 
} 

注意,第二种方法需要更改类型你的Image属性的BitmapSource(甚至更好,ImageSource),这将提供更大的灵活性。


没有任何手动下载的替代方法可能如下所示。它也不需要冻结BitmatImage,因为它不是在任务线程中创建的。

public static Task<BitmapSource> GetNewImageAsync(Uri uri) 
{ 
    var tcs = new TaskCompletionSource<BitmapSource>(); 
    var bitmap = new BitmapImage(uri); 

    if (bitmap.IsDownloading) 
    { 
     bitmap.DownloadCompleted += (s, e) => tcs.SetResult(bitmap); 
     bitmap.DownloadFailed += (s, e) => tcs.SetException(e.ErrorException); 
    } 
    else 
    { 
     tcs.SetResult(bitmap); 
    } 

    return tcs.Task; 
} 
+0

无法设置'bitmap.UriSource'而不是'bitmap.StreamSource'来避免手动下载代码(保留所有缓存选项并冻结)? – Evk

+0

不是当你想立即冻结BitmapImage时。您可以注册一个DownloadCompleted事件处理程序,您可以将其冻结,但在处理程序被调用之前无法将其传递到UI。 – Clemens

+0

@Evk请参阅编辑的替代w/o手动下载。 – Clemens