views:

10190

answers:

4

I have a strange issue with WPF, I was loading images from the disk at runtime and adding them to a StackView container. However, the images were not displayed. After some debugging I found the trick, but it really doesn't make any sense. I've made a small demo app to identify the problem:

Create a new WPF project, and paste code as follows:

xaml:

<Window x:Class="wpfBug.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
    <StackPanel Name="sp">
    </StackPanel>
</Window>

xaml.cs, paste below default usings:

namespace wpfBug
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            Image i = new Image();
            BitmapImage src = new BitmapImage();
            src.BeginInit();
            src.UriSource = new Uri("picture.jpg", UriKind.Relative);
            src.EndInit();
            i.Source = src;
            i.Stretch = Stretch.Uniform;
            //int q = src.PixelHeight;        // Image loads here
            sp.Children.Add(i);
        }
    }
}

Copy a image to the bin/Debug folder and call it 'picture.jpg'

This program doesn't display anything, unless the commented line is uncommented.

Can anyone explain what I'm doing wrong, or why this happens? If you remove the image and run the program it generates an exception on the 'int q= ...' line. If that line is commented the program runs without exceptions even if no image is present. Loading an image only if nessesary makes sense, but then the image should be loaded when I add the Image control to the StackPanel.

Any ides ?

Edit: By the way, if you add the image as a resource, the 'int q = ..' line is not needed.

A: 

This is strange behavior and although I am unable to say why this is occurring, I can recommend some options.

First, an observation. If you include the image as Content in VS and copy it to the output directory, your code works. If the image is marked as None in VS and you copy it over, it doesn't work.

Solution 1: FileStream

The BitmapImage object accepts a UriSource or StreamSource as a parameter. Let's use StreamSource instead.

        FileStream stream = new FileStream("picture.png", FileMode.Open, FileAccess.Read);
        Image i = new Image();
        BitmapImage src = new BitmapImage();
        src.BeginInit();
        src.StreamSource = stream;
        src.EndInit();
        i.Source = src;
        i.Stretch = Stretch.Uniform;
        panel.Children.Add(i);

The problem: stream stays open. If you close it at the end of this method, the image will not show up. This means that the file stays write-locked on the system.

Solution 2: MemoryStream

This is basically solution 1 but you read the file into a memory stream and pass that memory stream as the argument.

        MemoryStream ms = new MemoryStream();
        FileStream stream = new FileStream("picture.png", FileMode.Open, FileAccess.Read);
        ms.SetLength(stream.Length);
        stream.Read(ms.GetBuffer(), 0, (int)stream.Length);

        ms.Flush();
        stream.Close();

        Image i = new Image();
        BitmapImage src = new BitmapImage();
        src.BeginInit();
        src.StreamSource = ms;
        src.EndInit();
        i.Source = src;
        i.Stretch = Stretch.Uniform;
        panel.Children.Add(i);

Now you are able to modify the file on the system, if that is something you require.

siz
A: 

You could try attaching handlers to various events of BitmapImage:

They might tell you a little about what's going on, as far as the image is concerned.

Drew Noakes
I tried that, none of the events were triggered. I'm using the same image for all tests, so there is nothing wring with the image btw ;)
sindre j
+7  A: 

It is because the Creation was delayed. If you want the picture to be loaded immediately, you can simply add this code into the init phase.

src.CacheOption = BitmapCacheOption.OnLoad;

like this:

src.BeginInit();
src.UriSource = new Uri("picture.jpg", UriKind.Relative);
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
redjackwong
I tested this and it works. This however proves that there is a bug in the framework, because if you set CacheOption to OnDemand the image is never loaded even if it should be loaded before beeing displayed on the screen. Thanks for the tip, I didn't notice that property. +1 ;)
sindre j
Helped me too :)
Vladimir Dyuzhev
A: 

I am using the same technique to load the images but my application becomes very slow if the number of images to be loaded is large.

Can someone suggest anything?

I guess you are loading and storing big images, if you rescale them after load and dispose of the original image you should be able to save memory. If you need lots of large images im memory, you will need to use a lot of memory no matter what ;)
sindre j