【问题标题】:WPF Image - server returned Forbidden (403)WPF 图像 - 服务器返回禁止 (403)
【发布时间】:2017-05-16 22:38:53
【问题描述】:

我有一个ListView 绑定到一个ObservableCollection。这个 ListView 使用一个 ItemTemplate,它只包含一个 Image 控件,它的 Source 属性绑定到一个 URL 字符串。

对于某些 URL,图像无法加载,因为远程服务器会返回“Forbidden (403)” - 这可以通过向获取图像的 HTTP 请求添加特定标头来解决,但是问题是我不知道应该如何修改上述请求。

我尝试了两种不同的方法:

  1. 创建IValueConverter 对象。我会自己获取 URL 并获取图像数据 - 将其放入 MemoryStream 并使用流初始化 BitmapImage 对象并将其返回给 Image 控件。事实证明,这种方法非常缓慢,并且阻塞了 UI 线程。
  2. 创建要绑定的新属性将返回包含图像数据的字节数组。此数据将在第一次使用任务调用时延迟初始化。当数据完成下载时,PropertyChanged 事件将被触发,这将在视觉上更新图像。这种方法不会阻塞 UI 线程,但由于某种原因速度极慢。

我想知道两件事:

  • 为什么我的方法明显变慢了?

  • 如何直接修改图像控件从远程服务器获取图像的方式,而不会对性能/速度造成太大影响?

考虑以下示例:

public byte[] ImageThumbnail 
{
    get 
    {
        if (img == null) img = GetImage(ImageUrls.Thumbnail);
        return img;
    }
}
public byte[] GetImage(string url) {
     HttpClient client = new HttpClient();
     // add some headers
     return client.GetByteArrayAsync(url).Result;
}

图像将绑定到“ImageThumbnail”,并将 IsAsync 设置为 True。在这种情况下,与仅将 URL 直接绑定到图像源相比,图像的下载速度要慢得多。

【问题讨论】:

  • “为什么我的方法明显变慢了?”除非您向我们展示您的代码,否则没有人能说出来。作为提示,您可能应该使用异步绑定(通过设置其 IsAsync 属性)。直接绑定到在 Binding 调用属性 getter 时(异步)创建的 ImageSource 属性。不要使用绑定转换器,因为它不会被异步调用。
  • 是的,我知道为什么绑定转换器不起作用,只是想也许有一种异步的方式来实现它。我已经尝试过使用异步绑定 - 我在问题中添加了一个示例。

标签: c# wpf image binding


【解决方案1】:

您应该为 ItemTemplate 中的 Image 控件的 Source 属性使用异步绑定:

<ListBox.ItemTemplate>
    <DataTemplate>
        <Image Source="{Binding Image, IsAsync=True}"/>
    </DataTemplate>
</ListBox.ItemTemplate>

由于现在在后台线程中调用了 Image 属性 getter,因此必须通过冻结使返回的 ImageSource 可以跨线程访问。

public class ImageItem
{
    private string url;

    public ImageItem(string url)
    {
        this.url = url;
    }

    public ImageSource Image
    {
        get
        {
            var image = new BitmapImage();
            var buffer = new WebClient().DownloadData(url);

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

            image.Freeze();
            return image;
        }
    }
}

使用BitmapFrame 而不是BitmapImage,Image 属性可能会写得更短一些。下面显示的BitmapFrame.Create 方法已经返回了一个冻结的BitmapFrame。

public ImageSource Image
{
    get
    {
        var buffer = new WebClient().DownloadData(url);

        using (var stream = new MemoryStream(buffer))
        {
            return BitmapFrame.Create(
                stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
        }
    }
}

【讨论】:

  • 这很好用。是不是因为你直接返回一个冻结的 ImageSource 而不是一个字节数组?
  • 是的。当您返回一个字节数组时,到 ImageSource 的自动转换(即创建 BitmapFrame)仍然发生在 UI 线程中。此处介绍的解决方案尽可能在后台线程中执行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多