読者です 読者をやめる 読者になる 読者になる

BitmapImage.SetSource(Stream)で画像が表示されないケースがあるバグ?

Microsoft情報で確認してないけど、明らかに Silverlight 2.0 beta 1 のバグと思われるケースに遭遇して丸1日ハマってしまった…。対処法がわかったのでメモとして残しておく事に。

やりたいこと

WebClient.OpenReadAsync() で 画像データの Stream を取得して、画面に表示するだけ。

バグの現象

WebClient.OpenReadAsync() で Stream の取得まではできているのだが、画面に表示する事ができないケースがある。(表示できる時もある)

いろいろ試した結果、以下の条件に該当する時に発生する事がわかった。

  • XAML に画像を表示する為の Source 属性が空の Image 要素が存在されている
  • ボタンクリックなどの、初期処理以外のイベントハンドラ中で 上記の Image の Source へ BitmapIpage を設定している
    • 表示する画像が BitmapImage が Uri ではなく、SetSource(Stream)で 設定されている。

バグが発生するコード

文章で書いても判り辛いと思うので、バグが発生するコードを。

Page.xaml
<UserControl x:Class="SilverlightImageBug.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <StackPanel x:Name="LayoutRoot" Background="White">
        <Button Content="表示" Height="24" x:Name="button" Click="button_Click" />
        <Image x:Name="image1" />
    </StackPanel>
</UserControl>
Page.xaml.cs
using System;
using System.Windows.Controls;
using System.Net;
using System.Windows;
using System.Windows.Media.Imaging;

namespace SilverlightImageBug
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
       }

        private void button_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            WebClient webClient = new WebClient();
            webClient.OpenReadCompleted += delegate(object _sender, OpenReadCompletedEventArgs _e)
            {
                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.SetSource(_e.Result);
                image1.Source = bitmapImage;
            };
            webClient.OpenReadAsync(new Uri("hoge.jpg", UriKind.Relative));
        }
    }
}

このプログラムを動かしても、画像が画面に表示されない。

対処法

このバグを回避する方法は2つ。

Image.Visibility = Visibility.Visible; を追加する。

SetSource() の後ろに、Image のインスタンスの Visibility へ Visibility.Visible を設定する1行を追加する。

        private void button_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            WebClient webClient = new WebClient();
            webClient.OpenReadCompleted += delegate(object _sender, OpenReadCompletedEventArgs _e)
            {
                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.SetSource(_e.Result);
                image1.Source = bitmapImage;
                image1.Visibility = Visibility.Visible; // ← この行を追加
            };
            webClient.OpenReadAsync(new Uri("hoge.jpg", UriKind.Relative));
        }
Image のインスタンスを必要な時に生成する

あらかじめ XAML に Image 要素を記述しておくのをやめて、表示が必要になったタイミングで Image のインスタンスを作って、レイアウトに Add() するように修正する。

        private void button_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            WebClient webClient = new WebClient();
            webClient.OpenReadCompleted += delegate(object _sender, OpenReadCompletedEventArgs _e)
            {
                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.SetSource(_e.Result);

                Image image = new Image();      // ← Imageのインスタンス自体を
                image.Source = bitmapImage;     //    動的に生成して、Add()
                LayoutRoot.Children.Add(image); //
            };
            webClient.OpenReadAsync(new Uri("hoge.jpg", UriKind.Relative));
        }

補足

どうやら XAML に Source が指定されていない Image 要素が記述されている場合、初期表示処理が終わった段階で Visibility が Visibility.Collapsed として扱われてしまうようだ。
しかし、その状態でも Visibility の値を確認すると、Visible が返ってくる。(これがバグだと思ってる根拠)