views:

37

answers:

2

My scenario is the following. I am creating a little math quiz application for my son and wanted to dynamically change the background ImageBrush of my canvas after each question is answered. I proceeded to embed my images (pngs) into a resource file and figured I would load them into an array and then randomly choose one and load it into the canvas.

The first problem I ran into was of course the fact that in the resource file the images were being stored as Bitmaps. So after some looking around on the Internet I finally figured out how to get them converted from Bitmap to BitmapImage objects using the following helper method:

    private BitmapImage FromResourceBitmap(Bitmap bitmap)
    {
        var result = new BitmapImage();

        using(var stream = new MemoryStream())
        {
            bitmap.Save(stream, ImageFormat.Png);

            stream.Position = 0;

            result.BeginInit();
            result.StreamSource = stream;
            result.EndInit();
        }

        return result;
    }

From there I created an ImageBrush from the BitmapImage and assigned it to the Background property of the canvas:

            var brush = new ImageBrush {ImageSource = m_Media.Screens[0]}; // m_Media.Screens[x] returns a BitmapImage...obviously.
            QuestionCanvas.Background = brush;

Unfortunately, this doesn't seem to work. When the application runs the background is pure white. My XAML doesn't describe any backgrounds and...well I'm confused. Any help would be greatly appreciated! Thank you!

+1  A: 

I'm wondering if perhaps your canvas is transparent, or perhaps you have another element on top of the canvas. I would take a look at Snoop on Codeplex to look at your visual tree and identify exactly what is going on. Also consider using triggers or codebinding to set the image for you when you move to the next question. Then you could just bind the Background to the template with the trigger, or the item holding the image and have it automatically update.

James Deville
Thanks for reminding me of Snoop!
Mateo
A: 

After looking through the various ImageBrush related classes, especially that of BitmapImage I started to think that the StreamSource property was simply referencing the stream instead of making a local copy within the BitmapImage object. So, the using statement within my helper method was in effect releasing the stream and therefore making the StreamSource of the BitmapImage null. The canvas then fell back to a plain white (#FFFFFFFF) SolidColorBrush (Thanks James for reminding me of Snoop).

So, to fix this I instead created a private List to hold the references to the various image streams and then point my BitmapImages to those references. I implemented IDisposable to release the various MemoryStreams when the GC came along. Here is the abbreviated code:

public class Media : IDisposable
{
    private readonly List<BitmapImage> m_Screens = new List<BitmapImage>();
    private readonly List<MemoryStream> m_BackingStreams = new List<MemoryStream>();

    public BitmapImage MainScreen { get; private set; }

    public List<BitmapImage> Screens
    {
        get
        {
            return m_Screens;
        }
    }

    public Media()
    {
        LoadScreens();
    }

    private void LoadScreens()
    {
        m_BackingStreams.Add(FromResourceBitmap(Properties.Resources.Screen_01));
        m_BackingStreams.Add(FromResourceBitmap(Properties.Resources.Screen_02));
        m_BackingStreams.Add(FromResourceBitmap(Properties.Resources.Screen_03));
        m_BackingStreams.Add(FromResourceBitmap(Properties.Resources.Screen_04));
        m_BackingStreams.Add(FromResourceBitmap(Properties.Resources.Screen_05));

        foreach (var stream in m_BackingStreams)
        {
            m_Screens.Add(FromResourceStream(stream));
        }
    }

    private BitmapImage FromResourceStream(Stream stream)
    {
        var result = new BitmapImage();

        result.BeginInit();
        result.StreamSource = stream;
        result.EndInit();

        return result;
    }

    private MemoryStream FromResourceBitmap(Bitmap bitmap)
    {
        var stream = new MemoryStream();

        bitmap.Save(stream, ImageFormat.Png);

        return stream;
    }

    public void Dispose()
    {
        if (m_BackingStreams.Count == 0 || m_BackingStreams == null) return;

        foreach (var stream in m_BackingStreams)
        {
            stream.Close();
            stream.Flush();
        }
    }

And here is what it looked like when I actually set the background of my Canvas during runtime:

MainMenuCanvas.Background = new ImageBrush(m_Media.Screens[0]);

That fixed it, as inelegant as it may be.

While researching I did come across this little bit of info in the BitmapImage.StreamSource documentation page on MSDN:

Set the CacheOption property to BitmapCacheOption.OnLoad if you wish to close the stream after the BitmapImage is created. The default OnDemand cache option retains access to the stream until the bitmap is needed, and cleanup is handled by the garbage collector.

( http://bit.ly/bitmapimagestreamsource )

However, when I tried to use the original solution with the CacheOption enum set to BitmapCacheOption.OnLoad it resulted in the same problem. I may be missing something here, but the obvious isn't so obvious I guess. :)

Mateo