使 WPF 图像加载异步

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16035300/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:36:42  来源:igfitidea点击:

Make WPF Image load async

wpfimageasynchronous

提问by user1130329

I would like to load Gravatar-Images and set them from code behind to a WPF Image-Control. So the code looks like

我想加载 Gravatar-Images 并将它们从代码后面设置为 WPF Image-Control。所以代码看起来像

imgGravatar.Source = GetGravatarImage(email);

Where GetGravatarImage looks like:

GetGravatarImage 看起来像:

BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri( GravatarImage.GetURL( "http://www.gravatar.com/avatar.php?gravatar_id=" + email) , UriKind.Absolute );
bi.EndInit();
return bi;

Unfortunately this locks the GUI when the network connection is slow. Is there a way to assign the image-source and let it load the image in the background without blocking the UI?

不幸的是,当网络连接速度很慢时,这会锁定 GUI。有没有办法分配图像源并让它在后台加载图像而不阻塞用户界面?

Thanks!

谢谢!

回答by JanW

I suggest you to use a Binding on your imgGravatar from XAML. Set IsAsync=trueon it and WPF will automatically utilize a thread from the thread pool to pull your image. You could encapsulate the resolving logic into an IValueConverter and simply bind the email as Source

我建议您在 XAML 的 imgGravatar 上使用绑定。在其上设置IsAsync=true,WPF 将自动利用线程池中的线程来拉取图像。您可以将解析逻辑封装到 IValueConverter 中,并将电子邮件绑定为 Source

in XAML:

在 XAML 中:

<Window.Resouces>
    <local:ImgConverter x:Key="imgConverter" />
</Window.Resource>

...


<Image x:Name="imgGravatar" 
       Source="{Binding Path=Email, 
                        Converter={StaticResource imgConverter}, 
                        IsAsync=true}" />

in Code:

在代码中:

public class ImgConverter : IValueConverter
{
    public override object Convert(object value, ...)
    {
        if (value != null)
        {
             BitmapImage bi = new BitmapImage();
             bi.BeginInit();
             bi.UriSource = new Uri( 
                 GravatarImage.GetURL(
                     "http://www.gravatar.com/avatar.php?gravatar_id=" + 
                      value.ToString()) , UriKind.Absolute 
                 );
             bi.EndInit();
             return bi;                
        }
        else
        {
            return null;
        }

    }
}

回答by Clemens

I can't see why your code would block the UI, as BitmapImage supports downloading image data in the background. That's why it has an IsDownloadingproperty and a DownloadCompletedevent.

我不明白为什么你的代码会阻止 UI,因为 BitmapImage 支持在后台下载图像数据。这就是为什么它有一个IsDownloading属性和一个DownloadCompleted事件。

Anyway, the following code shows a straightforward way to download and create the image entirely in a separate thread (from the ThreadPool). It uses a WebClientinstance to download the whole image buffer, before creating a BitmapImage from that buffer. After the BitmapImage is created it calls Freezeto make it accessible from the UI thread. Finally it assigns the Image control's Sourceproperty in the UI thread by means of a Dispatcher.BeginInvokecall.

无论如何,以下代码显示了一种直接在单独的线程(来自ThreadPool)中下载和创建图像的直接方法。WebClient在从该缓冲区创建 BitmapImage 之前,它使用一个实例下载整个图像缓冲区。创建 BitmapImage 后,它会调用Freeze以使其可从 UI 线程访问。最后它Source通过Dispatcher.BeginInvoke调用在 UI 线程中分配 Image 控件的属性。

ThreadPool.QueueUserWorkItem(
    o =>
    {
        var url = GravatarImage.GetURL(
           "http://www.gravatar.com/avatar.php?gravatar_id=" + email);
        var webClient = new WebClient();
        var buffer = webClient.DownloadData(url);
        var bitmapImage = new BitmapImage();

        using (var stream = new MemoryStream(buffer))
        {
            bitmapImage.BeginInit();
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.StreamSource = stream;
            bitmapImage.EndInit();
            bitmapImage.Freeze();
        }

        Dispatcher.BeginInvoke((Action)(() => image.Source = bitmapImage));
    });


EDIT: today you would just use async methods:

编辑:今天你只需要使用异步方法:

var url = GravatarImage.GetURL(
    "http://www.gravatar.com/avatar.php?gravatar_id=" + email);
var httpClient = new HttpClient();
var responseStream = await httpClient.GetStreamAsync(url);
var bitmapImage = new BitmapImage();

using (var memoryStream = new MemoryStream())
{
    await responseStream.CopyToAsync(memoryStream);

    bitmapImage.BeginInit();
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    bitmapImage.StreamSource = memoryStream;
    bitmapImage.EndInit();
    bitmapImage.Freeze();
}

image.Source = bitmapImage;