Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Experiments in Building a Silverlight Photo Control ( Part 2 )

Blogs

Mike Taulty's Blog

Elsewhere

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.


Posted Fri, Jul 18 2008 4:30 AM by mtaulty
Filed under:

Comments

Dew Drop - July 18, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - July 18, 2008 | Alvin Ashcraft's Morning Dew
on Fri, Jul 18 2008 7:44 AM
Mike Taulty's Blog wrote Experiments in Building a Silverlight Photo Control ( Part 3 )
on Sat, Jul 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...
Community Blogs wrote Silverlight Cream for July 20, 2008 -- #329
on Sun, Jul 20 2008 10:58 AM
Alex Golesh on Streaming Media, Michael Washington importing content, Joel Neubeck with Closed Captioning
Marc: My Words wrote Interesting stuff from the past few days
on Tue, Jul 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 wrote Interesting stuff from the past few days
on Tue, Jul 22 2008 5:52 AM
Interesting stuff from the past few days