Published Friday, July 18, 2008 4:30 AM by mtaulty

Experiments in Building a Silverlight Photo Control ( Part 2 )

Following on from this post. I wanted to make sure that I only ever had a maximum of [Previous, Current, Next] images loaded in my control whilst still pro-actively trying to load an image as a user "heads towards it".

That is, if the user is on image N then I want to try and make sure I've already loaded N-1, N, N+1 as a way of making sure I've got their "next" image already.

How to do that? Previously, I just kept a Dictionary<int,MemoryStream> around where the int is the index of the image and the MemoryStream is the bytes of the image and this Dictionary would just grow and grow.

There's only one place in my code where I add to that Dictionary so ( rather than going off and building some LRU Dictionary class ) I just made sure that if my dictionary has grown a little large then I just trim it.

Whilst there, I decided that using a Lambda statement for the completion of WebClient.OpenReadCompleted was a bit ugly so refactored that into a separate function as well. I also noticed that I'd added 2 DependencyProperty's to my ImageLoader class and I've no idea why so I took those away. The code for my ImageLoader class now looks like.

  public class ImageLoader : INotifyPropertyChanged
  {
    public ImageLoader(List<PhotoSource> photos) 
    {
      this.photos = photos;
      imageMap = new Dictionary<int, MemoryStream>();
    }
    public void Initialise()
    {
      LoadImages();
    }
    public void MoveToNextImage()
    {
      if (HasNextImage)
      {
        currentIndex++;
        LoadImages();
        FirePropertyChanged("HasNextImage");
        FirePropertyChanged("HasPreviousImage");
      }
    }
    public void MoveToPreviousImage()
    {
      if (HasPreviousImage)
      {
        currentIndex--;
        LoadImages();
        FirePropertyChanged("HasNextImage");
        FirePropertyChanged("HasPreviousImage");
      }
    }
    public int CurrentIndex
    {
      get
      {
        return (currentIndex);
      }
    }
    public bool HasNextImage
    {
      get
      {
        return (currentIndex < photos.Count - 1);
      }
    }
    public bool HasPreviousImage
    {
      get
      {
        return (currentIndex > 0);
      }
    }
    private MemoryStream CheckImageLoaded(int i)
    {
      MemoryStream ms = null;

      lock (imageMap)
      {
        if (imageMap.ContainsKey(i))
        {
          ms = imageMap[i];
        }
      }
      return (ms);
    }
    private void LoadImages()
    {
      int minIndex = Math.Max(0, currentIndex - 1);
      int maxIndex = Math.Min(photos.Count - 1, currentIndex + 1);

      for (int i = minIndex; i <= maxIndex; i++)
      {
        LoadImage(i);
      }
    }
    private void LoadImage(int i)
    {
      MemoryStream ms = CheckImageLoaded(i);

      if (ms == null)
      {
        WebClient client = new WebClient();

        client.OpenReadCompleted += OnReadImageCompleted;

        client.OpenReadAsync(new Uri(photos[i].Source, UriKind.RelativeOrAbsolute), i);
      }
      else if (i == currentIndex)
      {
        FireImageAvailable(ms);
      }
    }
    void OnReadImageCompleted(object sender, OpenReadCompletedEventArgs e)
    {
      int index = (int)e.UserState;

      MemoryStream readMs = new MemoryStream();
      e.Result.WriteTo(readMs);
      e.Result.Close();
      readMs.Seek(0, SeekOrigin.Begin);

      lock (imageMap)
      {
        imageMap[index] = readMs;

        // New bit, try and trim the dictionary 
        if (imageMap.Keys.Count > 3)
        {
          int minKey = imageMap.Keys.Min();
          int maxKey = imageMap.Keys.Max();

          int minDistance = Math.Abs(minKey - index);
          int maxDistance = Math.Abs(maxKey - index);

          int evictKey = minDistance > maxDistance ? minKey : maxKey;

          imageMap.Remove(evictKey);
        }
      }
      if (index == currentIndex)
      {
        FireImageAvailable(readMs);
      }
    }
    private void FireImageAvailable(Stream stream)
    {
      if (ImageStreamAvailable != null)
      {
        ImageStreamAvailable(this,
          new StreamEventArgs() { Stream = stream });
      }
    }
    private void FirePropertyChanged(string property)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this,
          new PropertyChangedEventArgs(property));
      }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    Dictionary<int, MemoryStream> imageMap;
    public event EventHandler<StreamEventArgs> ImageStreamAvailable;
    private int currentIndex;
    private List<PhotoSource> photos;
  }

So, "onwards and upwards" - next stop needs to be introducing visual states I think and the transitions between them.


Filed Under:

# Dew Drop - July 18, 2008 | Alvin Ashcraft's Morning Dew @ Friday, July 18, 2008 7:44 AM

PingBack from http://www.alvinashcraft.com/2008/07/18/dew-drop-july-18-2008/

Dew Drop - July 18, 2008 | Alvin Ashcraft's Morning Dew

# Experiments in Building a Silverlight Photo Control ( Part 3 ) @ Saturday, July 19, 2008 3:59 AM

Following on from this post, and with reference to these posts by scorbs and this one by Ian, I thought...

Mike Taulty's Blog

# Silverlight Cream for July 20, 2008 -- #329 @ Sunday, July 20, 2008 10:58 AM

Alex Golesh on Streaming Media, Michael Washington importing content, Joel Neubeck with Closed Captioning

Community Blogs

# Interesting stuff from the past few days @ Tuesday, July 22, 2008 5:52 AM

These days, it’s not so much email as RSS that causes the delays when I get back from leave. Here’s a

Marc: My Words

# Interesting stuff from the past few days @ Tuesday, July 22, 2008 5:52 AM

Interesting stuff from the past few days

Marc: My Words