4

I'm trying to display images of various file types (including animated .gif files) in my Winforms application. I also have to be able to modify the files that are shown. (change the file name, delete them).

The problem is that a Picturebox locks the image file until the application is closed when using the normal way.

That means I can't do this:

private void Form1_Load(object sender, EventArgs e)
    {

        PictureBox pic = new PictureBox();
        pic.Size = new Size(250, 250);
        pic.Image = Image.FromFile("someImage.gif");
        this.Controls.Add(pic);

                    //No use to call pic.Image = null or .Dispose of it
        File.Delete("someImage.gif"); //throws exception
    }

The workaround in the link above is as follows:

    private void Form1_Load2(object sender, EventArgs e)
    {
        PictureBox pic = new PictureBox();
        pic.Size = new Size(250, 250);
                    //using a FileStream
        var fs = new System.IO.FileStream("someImage.gif", System.IO.FileMode.Open, System.IO.FileAccess.Read);
        pic.Image = System.Drawing.Image.FromStream(fs);
        fs.Close();

        this.Controls.Add(pic);

        pic.MouseClick += pic_MouseClick;
    }

That works fine for normal image types, but it won't load animated .gifs, which is important to me. Trying to load one will make it look like this.

I've found a few other topics about it (this and this) but they're all about WPF and use BitmapImage. I've searched about how to use BitmapImage in a Winforms application, but haven't found anything apart from that it is supposed to somehow work.

I would like to stay with Winforms because I'm just getting used to it, but that's not a necessity.

To summarize: I need a way to show common image types (png, jpg, bmp, and animated gif) while still being able modify the file on the HDD. It is OK if that means unloading->modifying->reloading the file. I'd prefer Winforms, but other Frameworks would do.

Thanks for your help.

Edit: Another way I've tried

using (System.IO.FileStream fs = new System.IO.FileStream("E:\\Pics\\small.gif", System.IO.FileMode.Open, System.IO.FileAccess.Read))
        {
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            fs.CopyTo(ms);
            pic.Image = Image.FromStream(ms);
        } 

But shows the same problem as the second example. The gif doesn't load.

s3rius
  • 1,442
  • 1
  • 14
  • 26
  • 1
    Open it as a memorystream (copy the bytes from the filestream) then load it into a gdi object that you can feed to the picturebox. This way there is no link to the file. – woutervs Feb 25 '14 at 14:07
  • 1
    Well there is a trick to be done, when working with GIF's on a MemoryStream: http://stackoverflow.com/questions/8763630/c-sharp-gif-image-to-memorystream-and-back-lose-animation – MrPaulch Feb 25 '14 at 14:15
  • @woutervs I've edited the post with another code snippet. Is that what you meant? If not, could you elaborate? – s3rius Feb 25 '14 at 14:25
  • Try my answer. You shouldn't even need a FileStream in this case. – MrPaulch Feb 25 '14 at 14:30
  • Hans Passant answer is the right way to copy a filestream to a memorystream indeed. MrPaulch answer might be a solution for .net 4.5 (Did not test this myself.) – woutervs Feb 25 '14 at 14:51

2 Answers2

10

Using a MemoryStream is indeed the right way to avoid the file lock. Which is a strong optimization btw, the lock is created by the memory-mapped file that the Image class uses to keep the pixel data out of the paging file. That matters a great deal when the bitmap is large. Hopefully not on an animated gif :)

A small mistake in your code snippet, you forgot to reset the stream back to the start of the data. Fix:

 using (var fs = new System.IO.FileStream(...)) {
     var ms = new System.IO.MemoryStream();
     fs.CopyTo(ms);
     ms.Position = 0;                               // <=== here
     if (pic.Image != null) pic.Image.Dispose(); 
     pic.Image = Image.FromStream(ms);
 } 

In case it needs to be said: do not dispose the memory stream. That causes very hard to diagnose random crashes later, pixel data is read lazily.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Another way to reset the stream => ms.seek(seekposition.begin, 0) (code from my memory might not be entirely correct;)) – woutervs Feb 25 '14 at 14:52
  • I'll include that in my answer once I'm at my workstation again . – MrPaulch Feb 25 '14 at 15:06
  • Both answers work, but this one seems to be more universal. Since it works with all image types without modification. So I'll accept this one. Thanks for you help. – s3rius Feb 25 '14 at 16:17
  • If this is an operation that happens a lot, you'll need to keep track of that `MemoryStream` so you can dispose it after disposing the image, though. – Nyerguds Jan 24 '18 at 13:59
  • That is nonsense, MemoryStream does not use any unmanaged resources that could benefit from disposal. The only reason it has a Dispose method is because it inherits the one from Stream, it can't uninherit the method. And of course it doesn't override Dispose(bool), nothing to do. – Hans Passant Jan 24 '18 at 14:06
  • @HansPassant That is useful to know. Is there any way to know this other than looking at the source code? I didn't see anything to this effect in the documentation. – David Mercer Feb 24 '20 at 19:27
2

Essentialy you'll have to make a copy of the image file in your memory.

Pre .Net 4.0 (2.0,3.0,3.5) you'd have to create a FileStream and copy it to a MemoryStream and rewind it, as seen in another answer.

Since .Net 4.0 (4.0,4.5,...) Image.FromFile supports animated GIF's


If you work with .Net 4.0 or later following method will suffice:

Using System.IO, System.Drawing and System.Drawing.Imaging

 private void Form1_Load(object sender, EventArgs e)
    {
        string szTarget = "C:\\someImage.gif";

        PictureBox pic = new PictureBox();
        pic.Dock = DockStyle.Fill;

        Image img = Image.FromFile(szTarget);   // Load image fromFile into Image object

        MemoryStream mstr = new MemoryStream(); // Create a new MemoryStream
        img.Save(mstr, ImageFormat.Gif);        // Save Image to MemoryStream from Image object

        pic.Image = Image.FromStream(mstr); // Load Image from MemoryStream into PictureBox
        this.Controls.Add(pic); 

        img.Dispose(); // Dispose original Image object (fromFile)
        // after this you should be able to delete/manipulate the file

        File.Delete(szTarget);
    }
MrPaulch
  • 1,423
  • 17
  • 21
  • Not a good idea, Image.Save() does not support saving animated gifs. Have you tested this? – Hans Passant Feb 25 '14 at 14:39
  • Yes Tested it on said Environment. The GIF was displayed and animated correctly. I was surprised as well, since I thought I ll have to use a FileStream and a copy it to a MemoryStream, but apparently Image supports animated GIFs at least in .Net 4.5 – MrPaulch Feb 25 '14 at 14:44