1

Alright it has come to this. I searched this website among many others and no one can seem to give me a straight answer so I'm going to try just asking outright. Been on this issue for about a solid 3 days and I can't afford to waste any more time on it.

Goal: The app I am building is in WPF and is going to be used as a bug tracker for a project my design team and I will be undertaking soon. Since we are going to be building a game in C++ most of the errors that occur will have a visual element to them so I inlcuded functionality to provide an image of the error in question when the user adds a bug to the list. I then take that image and save it to a local directory (for testing). Now the image path in the Error object points to a path that leads to the local directory. This functionality has been tested and works fine. My problem showes up when I want to delete a bug from the list. I am getting that very infamous "IO Exception" saying that the image I want to delete is being used by another process.

So Far: At first I tried very elegant solutions, but as with all things you get to a point where you just want to see if you can get the thing to even work at all. So I am at the point where most of the code I am using is experimental and radical. So please when looking at it note that the code being used is out of desperation, so any "simple" solutions have probably already been tried (I did research this a lot becuase I hate having to do this). Things i can think of off the top of my head are the obsurd amount of disposes and forced garbage collections being called so please to not comment on the negative nature of this practice, I am well aware :).

The Code

Saving image to local directory

public void OnBrowseClick()
    {
        Microsoft.Win32.OpenFileDialog openBox = new Microsoft.Win32.OpenFileDialog();

        // Show dialog box to user and grab output
        Nullable<bool> result = openBox.ShowDialog();

        if (result == true)
        {
            // Create temp variable to hold local path string
            string localPath = Directory.GetCurrentDirectory();      

            // Grab the extension of the specified file path
            string extension = openBox.FileName.Substring(openBox.FileName.LastIndexOf("\\"));

            // Add extension to local path
            localPath += extension;

            // Create local copy of image at given file path (being ridiculous at this point)
            using (Stream stream = new FileStream(openBox.FileName, FileMode.Open, FileAccess.ReadWrite))
            {
                using (Bitmap bmp = LoadImage(stream))
                {
                    using (Bitmap temp = (Bitmap)bmp.Clone())
                    {
                        temp.Save(localPath);
                        temp.Dispose();
                    }

                    bmp.Dispose();
                }

                stream.Dispose();
            }

            // Set the URL in the image text box (UI stuff)
            LocalError.ImagePath = localPath;   
        }
    }

The following is the LoadImage function that is used in the function above

private Bitmap LoadImage(Stream stream)
    {
        Bitmap retval = null;

        using (Bitmap bitmap = new Bitmap(stream))
        {
            retval = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat);

            using (Graphics gdi = Graphics.FromImage(retval))
            {
                gdi.DrawImageUnscaled(bitmap, 0, 0); 
                gdi.Flush();
                gdi.Dispose();
                bitmap.Dispose();
            }
        }

        // Garbage collection here to be safe
        GC.WaitForPendingFinalizers();
        GC.Collect();

        return retval;
    } 

And finally we come to where I try to delete the image

public void OnDeleteClick()
    {
        // Ask user to make sure they want to delete selected item(s)
        MessageBoxResult result = MessageBox.Show("Are you sure you want to delete selected item(s) from the list?",
                                "Delete", MessageBoxButton.YesNo);

        if (result == MessageBoxResult.Yes)
        {
            for( int i = 0; i < Parent.ErrorListControl.ErrorDataGrid.SelectedItems.Count; ++i)
            {
                // Get path to image
                string path = (Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i] as Error).ImagePath;

                // Even tried calling garbage collection here!!!!!
                System.GC.WaitForPendingFinalizers();
                System.GC.Collect();
                File.Delete(path);

                // Remove the error from the error list
                Parent.ErrorListVM.ErrorList.Remove((Error)Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i]);

                // Decrement counter because we altered the list while in a loop
                i--;
            }
        }
    }

Notes: If anyone would like me to explain anything further or if you need to know something I left out please just ask I will get back to you ASAP! Any suggestions are helpful at this point I have absolutley no idea what I am doing wrong. I generally only program in a C++ environment so I tend to manage my own memory this whole "garbage collection" thing is really throwing a wrench in our project! (Off topic note: I do not know why I am not getting any color highlighting so I apologize to anyone who takes the time to read this).

Marc
  • 13
  • 6
  • Can you clarify the source of the original image? Is your application creating it, or does the user supply whichever image they choose to by clicking the Browse button? The Delete function, is it supposed to delete the original image that the user pointed using the OpenFileDialog? – Trevor Elliott Sep 14 '12 at 15:18
  • Hi, thank you for taking the time to help me. – Marc Sep 14 '12 at 15:25
  • Sorry, pressed enter The original image is chosen by the user via the browse button. All I do is load it in and store it somewhere else (local folder,bin -> debug) The intention is to delete the local copy of the image, not the original image. This would simply delete the image that was saved to the "Bin->Debug" folder in my project (just using this directory for testing). – Marc Sep 14 '12 at 15:26
  • Are you not able to just use File.Copy to copy the file to the local store? – Trevor Elliott Sep 14 '12 at 15:33
  • It may also be worth mentioning that I am using the MVVM design pattern - losely. – Marc Sep 14 '12 at 15:35
  • I am quit new to the .NET framework so I wasn unaware of that functionality. I'll get back to you on that. – Marc Sep 14 '12 at 15:36
  • I know this doesn't really help but you shouldn't need to call Dispose explicitly on your object which are within the "using" pattern sections, as they will automatically get cleaned. – Grofit Sep 14 '12 at 15:38
  • It does put the image in the correct directory, but No, I still have the same issue when a deletion of the image is attempted. – Marc Sep 14 '12 at 15:39
  • As mentioned above I am quit aware of this; This is one of those "Im desperate" situations. – Marc Sep 14 '12 at 15:39
  • Try to put your OpenFileDialog() inside a using(..) statement. – prashanth Sep 14 '12 at 17:59

2 Answers2

0

Since your LoadImage method does simple copy of the image, why not use File.Copy(source, dest) and avoid all the bitmaps, drawings, etc? Your goal might be to modify local bitmap after it's created, but it can still be done after copy.

Also, when using the using block, explicit .Dispose() is not required, as using block does it for you:

using (var obj = new SomeDisposableObject())
{
    // code here
    // obj.Dispose(); <-- not needed, since...
} // ...at this point obj.Dispose is called automatically.
Jeti
  • 256
  • 1
  • 3
  • 9
  • Hi, thank you for you help, however; as mentioned above I am aware of the need to "not" call Dispose( ). Just trying anything at this point. Also I mentioned in a comment above that the File.copy() did save the image correctly, however; the same error is still thrown when I try to delete it! – Marc Sep 14 '12 at 15:41
  • If file is getting locked, this SO question might help: [image-file-locked-after-loading-in-wpf](http://stackoverflow.com/questions/7122008/image-file-locked-after-loading-in-wpf) – Jeti Sep 14 '12 at 15:46
0

Here's a simple way to do what you want. In this example, I'm using Path.GetTempFileName() to generate a random file name in the local user's temp directory. If you don't need to persist the files then it's a good place to store them temporarily. Also, the user could theoretically import two files with the same name. So you want to use some kind of random filename generation or other mechanism to avoid conflicts.

private void browseButton_Click(object sender, RoutedEventArgs e)
{
    var openFileDialog = new Microsoft.Win32.OpenFileDialog();

    if (openFileDialog.ShowDialog(this) == true)
    {
        using (Bitmap originalImage = new Bitmap(openFileDialog.FileName))
        {
            string tempFileName = System.IO.Path.GetTempFileName();

            originalImage.Save(tempFileName);

            // LocalError.LocalPath
            LocalPath = tempFileName;
        }
    }
}

private void deleteButton_Click(object sender, RoutedEventArgs e)
{
    if (File.Exists(LocalPath))
    {
        File.Delete(LocalPath);
    }
}

Although a simple File.Copy should suffice as long as you have the right paths, I was just providing a solution that matched your question.

EDIT: Actually the current directory does not seem to be changed by the OpenFileDialog. I could swear that it did at some point. So I don't think this is your problem. Regardless, this code still works for me and you shouldn't require anything more complicated than this.

EDIT #2: It seems the lock is actually caused by the image being databound to the view and presumably locked by the BitmapSource. You should be able to create it without locking the file. Generally, this is slower so don't do it this way unless you need to be able to modify or delete the file.

bitmapSource = new BitmapImage();
bitmapSource.BeginInit();
bitmapSource.CacheOption = BitmapCacheOption.OnLoad;
bitmapSource.CreateOption = BitmapCreateOptions.IgnoreImageCache;
bitmapSource.UriSource = new Uri(ImagePath, UriKind.Absolute);
bitmapSource.EndInit();
Trevor Elliott
  • 11,292
  • 11
  • 63
  • 102
  • I do indeed need to persist the images, but for now I would just like to get this functionality working. I will try your solution and get back to you. – Marc Sep 14 '12 at 15:49
  • Edited my post. After testing it seems the OpenFileDialog doesn't change the current directory. I thought it did at some point. – Trevor Elliott Sep 14 '12 at 16:00
  • Are you accessing the image anywhere else, such as displaying it in a WPF view? – Trevor Elliott Sep 14 '12 at 16:00
  • I did try your example and got the same error, however; what Moozhe just said is something I did not take into account. Yes I have it displaying inside of an expander. The control is and is bound to the currently selected items image file path. – Marc Sep 14 '12 at 16:24
  • The code where you create the BitmapSource is what's locking the file then. You can create it in a way that does not lock the file. Refer to http://stackoverflow.com/questions/1688545/problems-overwriting-re-saving-image-when-it-was-set-as-image-source – Trevor Elliott Sep 14 '12 at 16:32
  • I will definitley check that out, for now I have to get back to work, but I will comment back after the weekend to let you know how it goes. Thank you everyone so far for your help!! This has been a good experience! – Marc Sep 14 '12 at 17:29