Saturday, October 18, 2008

WPF Cover Flow Tutorial : Part 5

After Part 4, we will partly refactor the code to build a custom component.

We define a new assembly called FlowComponent where we will move most of the code. First, the c# code will be part of a new class called FlowControl. This class herits from UserControl :
using[...]
namespace Ded.Tutorial.Wpf.CoverFlow.Part5.FlowComponent
{
public partial class FlowControl : UserControl
{
#region Fields
private int index;
private readonly List<Cover> coverList = new List<Cover>();
#endregion
#region Private stuff
private void RotateCover(int pos)[...]
private void UpdateIndex(int newIndex)[...]
private void viewPort_MouseDown(object sender, MouseButtonEventArgs e)[...]
#endregion
public FlowControl()
{
InitializeComponent();
}
public void Load(string imagePath)
{
coverList.Clear();
var imageDir = new DirectoryInfo(imagePath);
int doneImages = 0;
foreach (FileInfo image in imageDir.GetFiles("*.jpg"))
{
var cover = new Cover(image.FullName, doneImages++);
coverList.Add(cover);
visualModel.Children.Add(cover);
}
}
public void GoToNext()
{
if (index < coverList.Count - 1)
UpdateIndex(index + 1);
}
public void GoToPrevious()
{
if (index > 0)
UpdateIndex(index - 1);
}
}
}
The code from the TestWindow constructor is included in a new method called Load. It will build covers with the images from one directory. We also provide two new methods GoToNext and GoToPrevious to navigate between covers.

This is the same in the xaml code : we migrate the Grid (its background) and the Viewport3D to our custom UserControl :
<UserControl x:Class="Ded.Tutorial.Wpf.CoverFlow.Part5.FlowComponent.FlowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="282" Width="490">
<Grid ClipToBounds="True">
<Grid.Background[...]>
<Viewport3D x:Name="viewPort" Grid.Column="0" Grid.Row="0" ClipToBounds="False" MouseDown="viewPort_MouseDown">
<Viewport3D.Camera[...]>
<Viewport3D.Children[...]>
</Viewport3D>
</Grid>
</UserControl>
We can set the size we want. It will be overriden in the owner component. We set the ClipToBounds property to True to avoid covers from exceeding the bounds of our component.

The Cover class is also moved to the FlowComponent assembly.

As we are creating a CoverFlow component, it is a good idea to deal with rectangle images. We update the Point3D coordinates with the ImageSource size. The ImageSource will be stored as a class attribute.
private readonly ImageSource imageSource;
private double RectangleDx()
{
if (imageSource.Width > imageSource.Height)
return 0;
else
return 1 - imageSource.Width / imageSource.Height;
}
private double RectangleDy()
{
if (imageSource.Width > imageSource.Height)
return 1 - imageSource.Height / imageSource.Width;
else
return 0;
}
private Geometry3D Tessellate()
{
double dx = RectangleDx();
double dy = RectangleDy();
var p0 = new Point3D(-1 + dx, -1 + dy, 0);
var p1 = new Point3D(1 - dx, -1 + dy, 0);
var p2 = new Point3D(1 - dx, 1 - dy, 0);
var p3 = new Point3D(-1 + dx, 1 - dy, 0);
...
}
private Geometry3D TessellateMirror()
{
double dx = RectangleDx();
double dy = RectangleDy();
var p0 = new Point3D(-1 + dx, -3 + 3 * dy, 0);
var p1 = new Point3D(1 - dx, -3 + 3 * dy, 0);
var p2 = new Point3D(1 - dx, -1 + dy, 0);
var p3 = new Point3D(-1 + dx, -1 + dy, 0);
...
}
public Cover(string imagePath, int pos)
{
...
imageSource = LoadImageSource(imagePath);
...
}
With this new improvement, it is possible to use the component to browse photos :
There is nothing much left in the TestWindow class. In the xaml code, after we've referenced our component assembly, we just use our FlowControl as main Content :
<Window x:Class="Ded.Tutorial.Wpf.CoverFlow.Part5.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Coverflow" Width="512" Height="320" KeyDown="Window_KeyDown"
xmlns:flow="clr-namespace:Ded.Tutorial.Wpf.CoverFlow.Part5.FlowComponent;assembly=Ded.Tutorial.Wpf.CoverFlow.Part5.FlowComponent">
<flow:FlowControl x:Name="flow" Margin="0"></flow:FlowControl>
</Window>
In the C# code, we just keep the Window_KeyDown method and the constructor :
using System.Windows;
using System.Windows.Input;
namespace Ded.Tutorial.Wpf.CoverFlow.Part5
{
public partial class TestWindow : Window
{
#region Private stuff
private void Window_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Right:
flow.GoToNext();
break;
case Key.Left:
flow.GoToPrevious();
break;
}
}
#endregion
public TestWindow()
{
InitializeComponent();
flow.Load(@"c:\_covers");
}
}
}
That's it ! We've built a CoverFlow component reusable in other WPF applications. But this component needs improvements. We will add virtualization in the next part...
Continue with Part 6.
Edit 2014-02-23 : Code has moved to github.

5 comments:

Unknown said...

Good afternoon,

I wanted to tell you how great your blog is! I am currently learning WPF and I found your posts very useful! So thanks :)

I am now on the WPF Cover Flow Tutorial - Part 5, and I try to download the source code: http://vianney.philippe.googlepages.com/WPFCoverFlowTutorialPart5.zip

Unfortunately, the link is broken. Would you please be able to send me a new link or send me the files by email please? (inasonarmour@gmail.com)


Keep up the great job!

Thanks,
Romain

ded' said...

Links fixed (googlepages migration).

Unknown said...

Hi ded, I don't understand the part where you said "We define a new assembly called FlowComponent where we will move most of the code", What kind of component I must add? Could you explain me please I can't pass to the part 5.

Unknown said...
This comment has been removed by the author.
ded' said...

@Miguel The base component class is UserControl, you can download the source code at the bottom.