Wednesday, April 15, 2009

WPF Cover Flow Tutorial : Part 7

After some improvements in the Cover class, let's describe how virtualization works.

We do not keep all covers in memory. Remember the drawing in Part 2. At a given time, we only keep a few covers on both sides of the current cover (at position index). This means that every time we browse through the covers, we create and destroy one or more covers. If we do not keep all covers in memory, we have to keep enough information to rebuild covers : image file path and position. We also need to remember which covers are currently built.
public const int HalfRealizedCount = 6;
public const int PageSize = HalfRealizedCount;
private readonly ICoverFactory coverFactory;
private readonly Dictionary<int, ImageInfo> imageList = new Dictionary<int, ImageInfo>();
private readonly Dictionary<int, ICover> coverList = new Dictionary<int, ICover>();
private int index;
private int firstRealized = -1;
private int lastRealized = -1;
  • HalfRealizedCount is the number of covers created before and after current cover
  • I use the same value for the PageSize (we could choose a different value) when we will move by more than one cover at a time
  • imageList will map cover positions to image paths
  • coverList will be used as an array to keep realized covers
  • firstRealized and lastRealized are the positions of respectively first and last realized covers
There will be two ways to browse covers : by less than a page with animation or jumping by more than a page without animation.
    private void RotateCover(int pos, bool animate)
{
if (coverList.ContainsKey(pos))
coverList[pos].Animate(index, animate);
}
private void UpdateIndex(int newIndex)
{
if (index != newIndex)
{
bool animate = Math.Abs(newIndex - index) < PageSize;
UpdateRange(newIndex);
int oldIndex = index;
index = newIndex;
if (index > oldIndex)
{
if (oldIndex < firstRealized)
oldIndex = firstRealized;
for (int i = oldIndex; i <= index; i++)
RotateCover(i, animate);
}
else
{
if (oldIndex > lastRealized)
oldIndex = lastRealized;
for (int i = oldIndex; i >= index; i--)
RotateCover(i, animate);
}
camera.Position = new Point3D(Cover.CoverStep * index, camera.Position.Y, camera.Position.Z);
}
}
I'm not sure if the test in RotateCover is still needed. Here is how the new UpdateIndex function. We compute the animation boolean like described before. The UpdateRange function deals with cover realization : it will create and destroy covers as necessary and updates both realized indexes. Then, for all realized covers between old and new index, we rotate the covers. Finally, like before, we update camera positions.
    private void RemoveCover(int pos)
{
if (!coverList.ContainsKey(pos))
return;
coverList[pos].Destroy();
coverList.Remove(pos);
}
private void UpdateRange(int newIndex)
{
int newFirstRealized = Math.Max(newIndex - HalfRealizedCount, 0);
int newLastRealized = Math.Min(newIndex + HalfRealizedCount, imageList.Count - 1);
if (lastRealized < newFirstRealized || firstRealized > newLastRealized)
{
visualModel.Children.Clear();
coverList.Clear();
}
else if (firstRealized < newFirstRealized)
{
for (int i = firstRealized; i < newFirstRealized; i++)
RemoveCover(i);
}
else if (newLastRealized < lastRealized)
{
for (int i = lastRealized; i > newLastRealized; i--)
RemoveCover(i);
}
for (int i = newFirstRealized; i <= newLastRealized; i++)
{
if (!coverList.ContainsKey(i))
{
ICover cover = coverFactory.NewCover(imageList[i].Host, imageList[i].Path, i, newIndex);
coverList.Add(i, cover);
}
}
firstRealized = newFirstRealized;
lastRealized = newLastRealized;
}
In the UpdateRange method, we first compute realized indexes. Then, we remove necessary covers (might need all covers if we move by more than HalfRealizedCount. Finally, we create missing covers.
    public void GoToNext()
{
UpdateIndex(Math.Min(index + 1, imageList.Count - 1));
}
public void GoToPrevious()
{
UpdateIndex(Math.Max(index - 1, 0));
}
public void GoToNextPage()
{
UpdateIndex(Math.Min(index + PageSize, imageList.Count - 1));
}
public void GoToPreviousPage()
{
UpdateIndex(Math.Max(index - PageSize, 0));
}
Navigation methods are self explanatory.

Possible improvements :
  • Slider
  • Events
  • Tags (e.g. album names to jump to covers)
Source is available in next post.

2 comments:

Bruno Wouters said...

Great tutorial! Any idea when you will make the source available?

Thanks!

Dieg said...

I agree - great work!

I recently started to get more and more interest in WPF and this tutorial is defenetly one of the reasons.
waiting for next post & sources...