Leron
Leron

Reputation: 9886

.NET PictureBox - how to be sure that the resource is released

I have an OpenFileDialog and PictureBox in user control. To understand the problem better I'll explain in few words how this user control works. The user can select an image to be opened for the form. The name of this image is saved in a DataBase and the file for the image is copied in a default location. When there is some image saved in the database it is load in the picturebox when the form with the picturebox control is loaded. If the user select another image and want to save the form with the new image I have a method that takes care to delete the old image file from my default location and that is where the problem occurs.

When I have loaded image and try to save new one, sometimes (very rare in fact) I get an error that The resource is being used by another process.. I can paste the exact error if needed. I think that the problem is caused from the picturebox and the way it deals with images.

Here is my code:

if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    if (MyImage != null)
                    {
                        MyImage.Dispose();

                    }
                    selectedFile = openFileDialog1.FileName;
                    selectedFileName = openFileDialog1.SafeFileName;

                    MyImage = new Bitmap(openFileDialog1.FileName);
                    pictureBox1.Image = (Image)MyImage;

                    int imageWidth = pictureBox1.Image.Width;
                    int picBoxWidth = pictureBox1.Width;

                    if (imageWidth != 0 && picBoxWidth > imageWidth)
                    {
                        pictureBox1.Width = imageWidth;
                    }
                    else
                    {
                        pictureBox1.Width = defaultPicBoxWidth;
                    }
                }
                catch (Exception ex)
                {
                    logger.Error(ex.ToString());
                    MessageBox.Show("Error loading image!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

and my delete method:

public void DeleteImage(AppConfig imageInfo, string imageName)
        {
            string imgPath = imageInfo.ConfigValue.ToString();
            try
            {
                File.Delete(imgPath + "\\" + imageName);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

I thought that :

if (MyImage != null)
                        {
                            MyImage.Dispose();

                        }

will deal with this problem but still sometimes it occurs. And because it's not everytime it's even more critical to deal with it because at some point I may decide that I have solved it but in fact to be just lucky for some time.

Upvotes: 2

Views: 1160

Answers (4)

supercat
supercat

Reputation: 81307

A major difficulty with things like PictureBox is that because a PictureBox has no way of knowing whether it is the only user of an image, it consequently has no way of knowing whether it should dispose of that image when it no longer needs it.

Consequently, whatever code owns a picture box must also take ownership of the image associated therewith. There are three approaches I could suggest for doing so:

  • Create a control derived from PictureBox which documents itself as assuming ownership of any image given to it. Such a control should probably replace the image property with a SetImageWithOwnership method (with the semantics that once an image is passed to the PictureOwningBox, the box will be expected to "own" it, and will dispose it either when the box is Disposed or when a different image is given to the box).

  • Attach event handlers to a PictureBox to handle the scenarios where either the box is destroyed or a different image is assigned to it.

  • Have any code which would cause the PictureBox to be disposed or have a different image loaded, also dispose the Image that had been assigned to it.

While there may be cases where it would be appropriate to call GC.Collect and let the garbage-collector take care of things, such an approach is generally unsound.

Upvotes: 1

Hans Passant
Hans Passant

Reputation: 942368

    MyImage = new Bitmap(openFileDialog1.FileName);
    pictureBox1.Image = (Image)MyImage;

Yes, that code puts a lock on the file. The lock is produced by a memory mapped file object that GDI+ creates to efficiently map the pixel data of the file into memory without having to allocate space in the paging file. You will not be able to delete the file as long as the image is displayed in the picture box and not disposed, the lock prevents that. You will have to dispose the image and set the Image property back to null before you can delete the file.

You can prevent the file from getting locked by making an in-memory copy of the image:

    using (var temp = new Bitmap(openFileDialog1.FileName)) {
        pictureBox1.Image = new Bitmap(temp);
    }

It is not as efficient of course, to be avoided if the image is large. And do beware that another process may in fact have a similar lock on the file. Nothing you can do about that.

Upvotes: 2

Andrew Benton
Andrew Benton

Reputation: 506

I've had problems like this before, and one way that I've found to make sure that the resource is released, even after Dispose(), which really only marks the object for removal by the garbage collector, is by using GC.Collect(). I'm sure that there is a cleaner way to handle the resource disposal, but the time that it takes the GC.Collect() to run shouldn't hinder your program.

Upvotes: 0

fixagon
fixagon

Reputation: 5566

try that:

                using(Bitmap MyImage = new Bitmap(openFileDialog1.FileName))
                {
                  pictureBox1.Image = (Image)MyImage;

                  int imageWidth = pictureBox1.Image.Width;
                  int picBoxWidth = pictureBox1.Width;

                  if (imageWidth != 0 && picBoxWidth > imageWidth)
                  {
                      pictureBox1.Width = imageWidth;
                  }
                  else
                  {
                      pictureBox1.Width = defaultPicBoxWidth;
                  }
                }

Upvotes: 0

Related Questions