One of the most frustrating things you run into when working with image files, Images objects, and PictureBoxes is that once you release your application, some users will get “file locked” errors when trying to manipulate files that you have displayed onto the screen via a picturebox. Finding a workaround can be downright maddening: It is sometimes hard to replicate the problem, and it is difficult to open an image and display it without displaying the image you have just opened from a file .
It does not help that on some machines the problem manifests itself as an “unknown GDI+ error,” so you might spend many hours chasing this unknown error only to realize it’s the same as the “file in use by another process” error you get from other machines.
Microsoft makes the problem worse, giving misinformation in Knowledge Base articles such as these: http://support.microsoft.com/kb/311754. This article gives a work around that does not fix the problem everywhere and it goes on to state this behavior is “by design.” Putting aside the issue of how someone could put in bugs such as these “by design,” the fact that the behavior is not consistent makes this a bug anyway.
If you do a search for the problem you might run into these suggested fixes:
- Dispose of the Image object before saving a file
- Dispose of the Image object before saving a file, and then call the garbage collector to make sure the Image really is disposed
- Make a copy of the Image before attaching it to the PictureBox (It seems .NET is way too smart to fall for that one and somehow still tracks where the image came from)
- Use the Image.Clone() method to make a copy of the Image before attaching to the PictureBox
- Use a FileStream to open the image
There are many, many posts on this issue all over the internet, and believe me, I read all of them, and none gave me a full answer, although all pointed me in the right direction: The less “connected” the image file was to the Image object, the better off everyone would be.
So how do we fix it? This seems to be a bug in Windows since at least the VB6 days, so we might as well work around for it.
After much hair pulling and trial and error, here is what finally worked for me: Open the file containing the image, and, in the most indirect, sneaky way possible, assign the file contents to the Image object which you will use to display the image in a PictureBox. Do the same when saving an Image to a file. Do not mix Image objects and files and everyone will be happy. Here are the general steps:
- To open an image file:
- Open the graphic file, but not into an Image object, open the file into a byte array.
- Convert the byte array into an Image object (the conversion will figure out which format (PNG, JPG, etc…) the file is using)
- Use that Image object to assign to a PictureBox to display to the user
- Do NOT call the Image’s .Save() method, as that ties the Image to a file. See below…
- To save an image file:
- Clone the image via the Image.Clone() method
- Save the cloned image to a MemoryStream, using your preferred encoding (PNG, JPG, etc…)
- Convert the stream to a byte array
- Save the byte array to a file
Here is some code that might help you:
To read the file:
public static Image NonLockingOpen(string filename) { Image result; #region Save file to byte array long size = (new FileInfo(filename)).Length; FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read); byte[] data = new byte[size]; try { fs.Read(data, 0, (int)size); } finally { fs.Close(); fs.Dispose(); } #endregion #region Convert bytes to image MemoryStream ms = new MemoryStream(); ms.Write(data, 0, (int)size); result = new Bitmap(ms); ms.Close(); #endregion return result; }
To write the file:
public static void NonLockingSave(Image img, string fn, ImageFormat format) { // Delete destination file if it already exists if (File.Exists(fn)) File.Delete(fn); try { #region Convert image to destination format MemoryStream ms = new MemoryStream(); Image img2 = (Image)img.Clone(); img2.Save(ms, format); img2.Dispose(); byte[] byteArray = ms.ToArray(); ms.Close(); ms.Dispose(); #endregion #region Save byte array to file FileStream fs = new FileStream(fn, FileMode.CreateNew, FileAccess.Write); try { fs.Write(byteArray, 0, byteArray.Length); } finally { fs.Close(); fs.Dispose(); } #endregion } catch { // Delete file if it was created if (File.Exists(fn)) File.Delete(fn); // Re-throw exception throw; } }
And finally, here is an extension method that will make your life easier:
public static void SaveViaStreams(this Image img, string fn, ImageFormat format) { NonLockingSave(img, fn, format); }
Once I used these methods to handle file opens/saves all my file locking errors went away, on all computers that used my system. Hope this helps you as well!