Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Channel9 3D Explorer with gestures (hybrid smart client)

4.88/5 (23 votes)
10 Mar 2009Ms-PL4 min read 34.6K   715  
An article on viewing Channel9's RSS posts
image.png

Introduction

I love Channel9 and I think this website must be on every developer's favorites list! A few days ago, I saw the "hybrid smart client" contest and I thought that this is a good opportunity to create a nice WPF application that connects me to Channel 9 in 3D way in order to view all of those great videos.

Background

A while ago, I created a WPF 3D View control, project hosted on Codeplex that inspired me of creating this 3D way of reading Channel 9's RSS. So, the WPF 3D view control was the first brick of this application. As every WPF developer knows (or should know), there's a very nice toolkit for putting 2D elements on a 3D content inside a common WPF application - content that is "clickable". The tookit's name is 3D Tools for Windows Presentation Foundation and it is also hosted on Codeplex.

Using the Code

First of all, I want to show you how can you work with the 3DView control. There are two ways of playing with this control: XAML and C#.

First Way: XAML

Create a standard WPF application, download the 3DView control and it's dependencies, add them to your project (Right click on References -> Add Reference->Browse->(look at the download location)->OK). Inside Window1.xaml, create a connection between your application and WPF 3D View assembly using xmlns.

After that, inside the Grid element, create a _3DViewControl element, set it's properties and add the children. A single child element,_3DViewElemet has a front element, _3dwns:_3DViewElement.FrontElement and a back element _3dwns:_3DViewElement.BackElement. Inside this elements you can put the actual 2D content.

The code is below.

XML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="Window_Loaded"
xmlns:_3dwns="clr-namespace:_3DView;assembly=3DView"
Title="Window1" Height="466" Width="639">
	<Grid Name="myGrid" > 

	<_3dwns:_3DViewControl x:Name="My3DView" 
                                    Height="{Binding ElementName=myGrid, Path=Height}"
				Width="{Binding ElementName=myGrid, Path=Width}" 
                                    TimeSpanMiliseconds="450">
		<_3dwns:_3DViewControl.Children>
			<_3dwns:_3DViewElement>
				<_3dwns:_3DViewElement.FrontElement>
					<Button>Click me1</Button>
				</_3dwns:_3DViewElement.FrontElement>
				<_3dwns:_3DViewElement.BackElement>
					<Button>Click me11</Button>
				</_3dwns:_3DViewElement.BackElement>
			</_3dwns:_3DViewElement>

			<_3dwns:_3DViewElement>
				<_3dwns:_3DViewElement.FrontElement>
					<Button>Click me2</Button>
				</_3dwns:_3DViewElement.FrontElement>
				<_3dwns:_3DViewElement.BackElement>
					<Button>Click me22</Button>
				</_3dwns:_3DViewElement.BackElement>
			</_3dwns:_3DViewElement>

			<_3dwns:_3DViewElement>
				<_3dwns:_3DViewElement.FrontElement>
					<Button>Click me3</Button>
				</_3dwns:_3DViewElement.FrontElement>
				<_3dwns:_3DViewElement.BackElement>
					<Button>Click me33</Button>
				</_3dwns:_3DViewElement.BackElement>
			</_3dwns:_3DViewElement>
		</_3dwns:_3DViewControl.Children>
	</Grid>

</Window>

Second Way: C#

In order to add the element dynamically, you have to remove all <_3dwns:_3DViewElement> elements. After that you should use the AddNewChild method described in the next section.

3DView Control Methods

A _3DViewControl derives from UserControl. So beside all of the UserControl methods, I've added some new methods for adding and removing new children.

C#
/// <summary>
/// Adds a new child
/// </summary>
/// <param name="child">Child to add</param>
public void AddNewChild(_3DView._3DViewElement child)
{

...
}

/// <summary>
/// Removes the element located at an index
/// </summary>
/// <param name="index">Element index to remove</param>

public void RemoveAt(int index)
{
...
}

As an example, I will show you how the Channel 9 RSS Explorer elements are added. So for the front element I've created a XAML UserControl and named it FrontDisplayControl. The markup of this element is:

XML
<Grid Background="White" Height="250">
    <Grid.RowDefinitions>
		<RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="10"/>
    </Grid.RowDefinitions>
    <TextBlock Margin="2" x:Name="TitleTextBlock"
           Style="{StaticResource CustomHeaderTextBlockStyle}" 
           HorizontalAlignment="Left" TextWrapping="Wrap" Grid.Row="0"/>
        
	<Grid Grid.Row="1" VerticalAlignment="Top">
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="80"/>
			<ColumnDefinition/>
		</Grid.ColumnDefinitions>
		
		<Image Margin="2" x:Name="MyImage"
                      Visibility="Visible" VerticalAlignment="Top"
                      HorizontalAlignment="Left" Grid.Column="0"/>
		<TextBlock Margin="3,0,3,0" Background="White"
                      x:Name="DescriptionTextBlock"
                      Style="{StaticResource NormalTextBlockStyle}" 
		Grid.Column="1" TextWrapping="Wrap"/>
	</Grid>

</Grid>

As you can see, this is where the image and the description of the post are.

For the back element, the markup is:

XML
<StackPanel Background="Transparent">
	<Grid HorizontalAlignment="Center" VerticalAlignment="Bottom">
		<Image x:Name="PlayImage" Source="..\Images\play.png" Width="25"
                      Height="25" HorizontalAlignment="Left"/>
		<Image x:Name="PauseImage" Source="..\Images\pause.png" Opacity="0"
                      Width="25" Height="25" HorizontalAlignment="Left"/>
	</Grid>
	<Grid>
		<MediaElement x:Name="MyPlayer" LoadedBehavior="Manual"
                      UnloadedBehavior="Stop"/>
	</Grid>
</StackPanel>

The back element has a MediaElement where you play the video. To see the video, all I have to do is to flip the element.

After reading the RSS from the website, you create a _3DViewElement, a FrontDisplayControl and a BackDisplayControl. For the front control set the image from <img> tag, title and the description and for the back element set the video URL. After that, call the AddNewChild method. That's it!

C#
//create a new element...
_3DViewElement element = new _3DViewElement();
//front element
FrontDisplayControl frontControl = new FrontDisplayControl(img);
frontControl.Title = __MyNodeItem["title"].InnerText;
frontControl.AddressLink = __MyNodeItem["link"].InnerText;
frontControl.Description = text;
//back element
BackDisplayControl backControl = new BackDisplayControl();
...
backControl.VideoLink = url;
element.FrontElement = frontControl;
element.BackElement = backControl;
this.My3DView.AddNewChild(element);

3DView Control Proprietes

As all controls, this has some specific properties.

C#
/// <summary>
/// Gets or sets the current item
/// </summary>
public int CurrentItem
{ get; set; }

/// <summary>
/// Gets or sets the current item offset on Z axis
/// </summary>
public double CurrentItemOffset { get; set; }

/// <summary>
/// Gets or sets the miliseconds in the animation duration
/// </summary>
public int Duration { get; set; }

/// <summary>
/// Sets the Visibility of Flip button
/// </summary>
public bool ViewFlipButton
{
	set
	{
		if (value == false)
			this.FlipButton.Visibility = Visibility.Collapsed;
		else
			this.FlipButton.Visibility = Visibility.Visible;
	}

	get
	{
		return this.FlipButton.Visibility == Visibility.Visible;
	}
}

/// <summary>
/// flip button content
/// </summary>
public string FlipButtonContent
{
	set
	{
		if (value == null)
			return;

		this.FlipButton.Content = value;
	}
}

/// <summary>
/// Gets or sets the Z coordonate of the camera position
/// </summary>
public double CameraZPosition
{
	set
	{
		Point3D point = this.MyCamera.Position;

		this.MyCamera.Position = new Point3D(point.X, point.Y, value);
	}
	get
	{
		return this.MyCamera.Position.Z;
	}
}

Table of properties:

Property name Description
CurrentItem Gets or sets the current item. It is used if you want to start at a diffrent item (like the middle item).
CurrentItemOffset Gets or sets the Z position of the current item.
Duration Time in miliseconds for the putting the current item into the stack and show the next one.
ViewFlipButton Gets or sets the visibility of the Flip button.
FlipButtonContent Gets or sets the content of the Flip button.
CameraZPosition The Z value of the camera.

Adding Gestures

Recently, I was able to play with an IBM Think Pad X41 that is a Tablet PC too, having a TrackPoint as a input device type. Of course, you can attach a mouse device but as long as you have the stylus, you don't need a mouse. So, I thought to make this application respond to gestures. All I did is override some mouse move methods, like OnMouseMove, OnMouseUp and OnMouseDown inside the 3D View control.

But first you have to remove the actions (Zoom and Track) inside the TrackballDecorator class (this is a 3D Tool class).

C#
/// <summary>
/// Previous position point
/// </summary>
Point __PreviousPosition;

/// <summary>
/// Overrides the OnMouseMove
/// </summary>
/// <param name="e">EventArgs</param>
protected override void OnMouseMove(MouseEventArgs e)
{
	base.OnMouseMove(e);

	if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
	{
		Point position = e.GetPosition(this);

		if (position == __PreviousPosition) return;

		if (position.Y < __PreviousPosition.Y - 50)
		{
			this.CameraZPosition -= 0.1d;
			__PreviousPosition = position;
		}

		if (position.Y > __PreviousPosition.Y + 50)
		{
			this.CameraZPosition += 0.1d;
			__PreviousPosition = position;
		}

		if (position.X > __PreviousPosition.X + 75)
		{
			this.MoveItem(RotateDirection.Left);

			__PreviousPosition = position;
		}

		if (position.X < __PreviousPosition.X - 75)
		{
			this.MoveItem(RotateDirection.Right);

			__PreviousPosition = position;
		}
	}
	else
		__PreviousPosition = e.GetPosition(this);
}

/// <summary>
/// Overrides the OnMouseDown
/// </summary>
/// <param name="e">EventArgs</param>
protected override void OnMouseDown(MouseButtonEventArgs e)
{
	base.OnMouseDown(e);

	__PreviousPosition = e.GetPosition(this);
}

/// <summary>
/// Overrides the OnMouseUp
/// </summary>
/// <param name="e">EventArgs</param>
protected override void OnMouseUp(MouseButtonEventArgs e)
{
	base.OnMouseUp(e);

	__PreviousPosition = e.GetPosition(this);
}

The gestures are:

  • If you want to see the next item (the right item), just drag and drop the current item from right to left.
  • If you want to see the previous item (the left item), just drag and drop the current item from left to right.
  • If you want to zoom in, just drag and drop from down to up.
  • If you want to zoom out, just drag and drop from up to down.

If you'll ask me why I choose these directions, I have only one answer: In my vision these actions are natural.

You can see in the image the application running on the IBM ThindPad.

gesture.jpg

I've created a video that shows these gestures.

Control Styles

I won't talk here about styling the application's user interface. If you want to know more about that, you can start here.

I hope that you'll use this application or at least the WPF 3D view control. :)

Hope I'll win this contest to buy myself a computer with multi-touch interface to make this application multi-touch enabled! :)

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)