6

I have a simple Window with button and second Window is opened when I click on the Button. Second Window has a Image control, which displays a .png-file. So if I use FileObject property for Binding all is OK, I can delete file from File Explorer. But if I use FileName property for Binding I cannot delete file from File Explorer, I get OS exception. I cannot do this even if I close second window, even if I invoke GC explicitly. What is the problem with FileName property? Any ideas?

Win 7, Net 4.0

Window1

<Grid>
    <Button Content="Ok"
            Width="100" 
            VerticalAlignment="Center" 
            HorizontalAlignment="Center" 
            Click="Click" 
            Padding="0,2,0,2" 
            IsDefault="True" 
            Name="_btnOk"/>
</Grid> 



public partial class Window : Window
{

    public Window()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void Click(Object sender, RoutedEventArgs e)
    {
        var window = new Window3();
        window.ShowDialog();
    }
}

Window2

<Grid>
    <Image Source="{Binding FileObject}"></Image>
</Grid>

public partial class Window2 : Window
{
    public Window2()
    {
        InitializeComponent();
        DataContext = this;
        FileName = "D:/pdf/myfile.png";

        Closing += Window2_Closing;
    }

    public String FileName { get; set; }

    public Object FileObject
    {
        get
        {
            if (String.IsNullOrEmpty(FileName))
                return null;

            if (!File.Exists(FileName))
                return null;

            var ms = new MemoryStream();
            var bi = new BitmapImage();

            using (var fs = new FileStream(FileName, FileMode.Open, FileAccess.Read))
            {
                fs.CopyTo(ms);
                bi.BeginInit();
                bi.StreamSource = ms;
                bi.EndInit();
            }
            return bi;
        }
    }

    void Window2_Closing(Object sender, System.ComponentModel.CancelEventArgs e)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}
monstr
  • 1,680
  • 1
  • 25
  • 44
  • Why are you binding like that? [Here's how to do it.](http://stackoverflow.com/questions/14358553/binding-image-source-through-property-in-wpf) – BCdotWEB Apr 14 '15 at 10:55
  • 1
    FYI, you don't need a MemoryStream in the `FileObject` property getter. Just set `bi.StreamSource = fs;` and `bi.CacheOption = BitmapCacheOption.OnLoad;`. The property should also return an `ImageSource` or `BitmapSource` instead of `object`. – Clemens Apr 14 '15 at 10:58

3 Answers3

12

When you bind the Image.Source property to an Uri (or a string, from which an Uri is created internally), WPF uses a built-in type converter that creates a BitmapFrame from the Uri.

If the Uri contains a path to a local file, the BitmapFrame keeps the file open, as long as it is existing. This may be longer than it is actually used in your application, because it may by cached by WPF.

When you need to be able to delete the file that an image was loaded from, you should always use your FileObject approach, but it should look like this:

public ImageSource Image
{
    get
    {
        ...
        var bi = new BitmapImage();
        using (var fs = new FileStream(FileName, FileMode.Open, FileAccess.Read))
        {
            bi.BeginInit();
            bi.CacheOption = BitmapCacheOption.OnLoad;
            bi.StreamSource = fs;
            bi.EndInit();
        }
        return bi;
    }
}

Or like this:

public ImageSource Image
{
    get
    {
        using (var fs = new FileStream(FileName, FileMode.Open, FileAccess.Read))
        {
            return BitmapFrame.Create(
                fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
        }
    }
}

Or you bind to the FileName property with a binding converter that creates a BitmapImage or BitmapFrame as shown above.

Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Got it, thx. But why you don't like `MemoryStream` and suggest `BitmapCacheOption.OnLoad` instead? What is the profit? In your second approach (with `BitmapFrame`). `BitmapFrame` will not be cached by WPF? – monstr Apr 14 '15 at 11:48
  • The benefit is that you save the `fs.CopyTo(ms)` operation, and that you can immediately close the stream (due to `BitmapCacheOption.OnLoad`). You left the memory stream open, which isn't actually a problem, but should nevertheless by avoided. In both cases no image is cached. Caching is only done with an Uri as key, which you don't have here. – Clemens Apr 14 '15 at 12:21
2

use this code, I will explain later what is the problem.

var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.UriSource = new Uri(FilePath);
image.EndInit();
return image;
Mujahid Daud Khan
  • 1,983
  • 1
  • 14
  • 23
1

You can solve this problem directly from xaml.

<Image>
    <Image.Source>
        <BitmapImage
            CacheOption="OnLoad"
            UriSource="{Binding FileName}" />
    </Image.Source>
</Image>
  • This throws the error: `InvalidOperationException: Property 'UriSource' or property 'StreamSource' must be set.`. I thought it might be that the bound value was a string instead of a `Uri` object, so I changed it to a `Uri` object when loading the dataset, but the same error was thrown while initializing the window. – Carmen DiMichele Jan 15 '23 at 15:07