Home > WPF > Swatting A 6 Month Old Bug

Swatting A 6 Month Old Bug

August 4, 2009

I began working on our first major WPF project last December.  Previously I had done a lot of playing and learning, and written a few internal utilities, but this was our first release grade project.  Things were going well and the partners were very happy with the software development… that is until “the bug”.

The Back Story

The software is an image management tool that interfaces with our legacy AS/400 application.  In short, it allows images on a network server to be linked with data records in our legacy application.  It is a replacement for an existing application written in FoxPro, so you can imagine the Oohs and Ahhs at the new UI.  The project also fills in some serious deficiencies in functionality.

As one can probably surmise, one important piece of functionality when managing groups of images is the ability to add, remove, or rename images.  With File IO such a rudimentary function, I had let it lapse until the end of the project.  Since WPF was the focus of my development, I had lost focus on the project itself.  I spent a lot of time making the application sing with animations, transitions, Visual States, drag and drop with a Visual Brush, you name it I wanted to try it.

Near the end of February I began working with the final bits, which included deleting an Image and saving an updated list of images back to the server.  As soon as I tried to perform any such action on any image, however, I would receive an IOException: "The process cannot access the file because it is being used by another process."

I worked intermittently on the issue while I continued improving the UI.  I figured it had to be something simple, so it took a little while until I began devoting serious effort to solving the problem.

Working on the Problem

Since the project was winding down, I began working on the problem in earnest about the middle of March.  I was able to determine that it was my application causing the lock, so I did a little research and found plenty of indication that opening images has a tendency to lock the file until the application ends.  With the idea that my application was holding the resource I tried to find a way to close it out until the operation was complete.

Without all the gory details, here is a list of things I tried in varying degrees, all of which failed to solve my problem:

  • Setting the ListBox.Source to null.
  • Adding a wait time before attempting the move.
  • Issuing GC.Collect().
  • Moving the operation to a different thread.

All of these were to no avail.  Eventually I posted the problem to StackOverflow and received two responses in short order.  I tried both of the solutions, but neither one seemed to solve my problem.  I asked a few friends on Twitter and email, but no one was able to help beyond what I had already tried.  By the end of April I was ready to call it quits.

Unfortunately for the project, but fortunately for my sanity, other projects became more important.  This gave me an official hiatus from the project, but I would still return to it occasionally in my spare time, always with the same results and frustrations.

Early in July my hiatus was over: the total project (the image project is a subproject of a larger effort) deadline is set at September 1st.  Panic set in: I have three applications to deliver in that time, and one of them is the Image manager.  I put it off a little while while I wrote one of the other applications, but on July 18th I returned full time to my nemesis.  The good news was there was little left to do: the bad news was that I had already spent countless hours on one stupid bug that was make or break for the entire application.

The Solution

I had little choice but to start from the beginning.  I went back to the StackOverflow discussion because it seemed to hold the most promise.  I wrote a test application that just performed the action I wanted, deleting an Image that was in the application.  By using a combination of the two methods in that thread I was able to get the feature working.

The solution that worked was to create a Value Converter that accepted the path of an image and returned a BitmapImage. 

[ValueConversion(typeof(string), typeof(BitmapImage))]
public class PathToBitmapImage : IValueConverter
{
    public static BitmapImage ConvertToImage(string path)
    {
        if (!File.Exists(path))
            return null;

        BitmapImage bitmapImage = null;
        try
        {
            bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new FileStream(path, FileMode.Open, FileAccess.Read);
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.EndInit();
            bitmapImage.StreamSource.Dispose();
        }
        catch (IOException ioex)
        {
        }
        return bitmapImage;
    }

    #region IValueConverter Members

    public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || !(value is string))
            return null;

        var path = value as string;

        return ConvertToImage(path);
    }

    public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

I then bound the Image source to the path of the image using the converter. While this image (or rather a local copy of the image) was on the screen, I was able to delete the file from the server.  I had tried a version of this previously, but I assumed it wasn’t working because I was still getting the error. Now I had proof that this code works.

Applying the Solution

I was able to apply the solution to the original application after some refactoring.  It’s amazing how much my coding style has changed from the last 6 months experience with WPF.  I look at the application now and cringe, especially at the code behind.  Anyway, I was able to shoe horn my solution in and the images showed correctly.  Can you imagine the anguish I felt, however, when the first time I tried to delete an image I received the same error?

I was quite dismayed, until I realized that it was not the actual image code since that had been proven: there had to be something else causing the problem, but what?  I had already gone through all the code and every reference to an image with a fine toothed comb, several times, what more could there be?  I began the process over again, but I was not a happy camper.

And then it dawned on me.  I finally knew what the problem was: I had been bitten by Data Binding.

How Can I Blame Data Binding?

To be fair, the issue is not with Data Binding per se, but my own misunderstanding of the object I was binding.  The images were bound not once, or twice, but three times:

  • ImageListBox – each item is bound to a different image, effectively a FilmStrip control
  • CurrentImage – a larger Image control showing the selected image from ImageListBox, bound to ImageListBox.SelectedItem
  • DeleteImageDialogBox – a separate Window that displays the image and prompts the user to confirm deletion, the Image is bound to the path passed in from the parent Window

For both ImageListBox and DeleteImageDialogBox I had used the new Value Converter.  CurrentImage, however was a different story.

In binding CurrentImage to the SelectedItem property of the ListBox, I was expecting the property to represent the rendered image that was the result of the Value Converter.  This is completely wrong!  The SelectedItem property is still the file path string!  CurrentImage was still executing the default behavior and retrieving the file from the path resulting in a file lock.

The Resolution

Once I understood what CurrentImage was binding to, I knew what to do: apply the same Value Converter I was using for the list box items.  Sure enough, once I applied that Value Converter everything else fell in place.

In hindsight I feel this was an “Oh Duh” sort of problem.  So many times the solution to our most frustrating problems is right in front of us, and almost always the solution is fairly simple.  As I said to a friend recently – it’s amazing sometimes that we ever get anything right!

Now I have a new issue: via the Value Converter I’m going to the file system three times to show the same image.  I’ll have to address that at some point, and I have a few ideas.  I could store the BitmapImage copy, or a byte[], or a MemoryStream locally and reuse it.  For now, however, I’m going to leave well enough alone and get on to my next application.  Hopefully it won’t take six months to finish.

Advertisements
Categories: WPF
%d bloggers like this: