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

Mike's Badges

Follow on Twitter
View mike's profile on slideshare
Add to Technorati Favorites
CW Blog Awards

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
(C) Mike Taulty, 2009. All rights reserved. The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Inappropriate comments will be deleted at the authors discretion. All code samples are provided "AS IS" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems