Hello guys! It was a long time ago I would like to upgrade most of my samples on the final release of WPF. For the first one, I decided to rewrite the Matrix Expander by adding some new features and improving its design. It always derived from the class Expander integrating sound and video but this time the media elements are directly embedded into the assembly and the control will be decorated in a 3D model in a full interactive way.
Notice that I previously wrote this article for the NetFxFactory community in french.
As usual, you can download the sources right here or just get the binaries (VS2008 and WPF 3.5 are required).
Intro
WPF 3.5 brings more important features to the Viewport3D by adding the Viewport2DVisual3D class that enables any control to be rendered as a full interactive 3D model. Once applied as a texture to a Model3D object, the 2D control stays now entirely functional. So I considered the design of the control in an independent way, as it would be used in any classic applications then I added the 3D decoration that I shaped depending on the final result to get. Therefor the control doesn't need to be designed with a view to be applied on a 3D model.
Custom expander
The control derives from the Expander base class and encapsulates the template to provide the visual tree (as the preceding). A simple templated ToggleButton is used to be bound to the IsExpanded property.
<Expander.Template>
<ControlTemplate TargetType="{x:Type lc:RDExpander}">
<Grid x:Name="EnterTheMatrix" Width="168" Height="210">
<Grid>
<Grid x:Name="_expanderContent" Panel.ZIndex="0">
<Image Source="../Images/MatrixFrame.png" Width="160" Height="101" Stretch="Uniform" StretchDirection="Both" />
<ContentPresenter />
<Grid.RenderTransform>
<TranslateTransform X="0" Y="0" />
</Grid.RenderTransform>
</Grid>
<Image x:Name="PART_MatrixBg" Width="168" Height="109" Stretch="Uniform" StretchDirection="Both" Source="../Images/MatrixScreen.png" Panel.ZIndex="10" VerticalAlignment="Top" />
<Border x:Name="PART_Border" Width="168" Height="101" Background="Transparent" Panel.ZIndex="-10" VerticalAlignment="Bottom" />
</Grid>
<ContentPresenter x:Name="_header" ContentSource="Header" />
<ToggleButton x:Name="_matrix" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}">
<ToggleButton.Template>
<ControlTemplate>
<Grid>
<MediaElement x:Name="MatrixMovie" Width="135" Height="77" Stretch="UniformToFill" Opacity="0">
<MediaElement.Triggers>
<EventTrigger RoutedEvent="MediaElement.MediaOpened">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.5" To="1" Storyboard.TargetProperty="Opacity" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</MediaElement.Triggers>
</MediaElement>
</Grid>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<MediaTimeline Source="{local:EmbeddedMedia Source='pack://application:,,,/FX/matrix.avi'}" RepeatBehavior="Forever" Storyboard.TargetName="MatrixMovie" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
The difficulty is to provide the right behavior according to the direction of the expander. In this case, I decide to code the behaviors only for the Down and Up directions. The others are similar to the Down. It seems that several ways are possible to animate the behavior of the expander. For me, I simply override the OnExpanded method and execute the adequat storyboards.
protected override void OnExpanded()
{
base.OnExpanded();
Grid __expanderContent = (Grid)base.GetTemplateChild("_expanderContent");
switch (base.ExpandDirection)
{
case ExpandDirection.Up:
__expanderContent.BeginStoryboard((Storyboard)this.Resources["Expanded_Up"]);
break;
case ExpandDirection.Down:
case ExpandDirection.Left:
case ExpandDirection.Right:
default:
__expanderContent.BeginStoryboard((Storyboard)this.Resources["Expanded_Down"]);
break;
}
}
Another modification of the control resides in the integration of the sound and video files into the application. Indeed, the control MediaElement still does not accept URI of ressources embedded into the assembly. A MarkupExtension object named EmbeddedMedia will allow this. The only constraint is the creation of a specific hidden folder on the disk in order to copy the files before loading and playing them.
<MediaTimeline Source="{local:EmbeddedMedia Source='pack://application:,,,/FX/matrix.avi'}" RepeatBehavior="Forever" Storyboard.TargetName="MatrixMovie" />public sealed class EmbeddedMediaExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
StreamResourceInfo __resourceStream = Application.GetResourceStream(this._source);
DirectoryInfo __directoryInfo = new DirectoryInfo(string.Format("{0}\\{1}", Directory.GetCurrentDirectory(), CACHE_FOLDER));
if (!__directoryInfo.Exists)
{
__directoryInfo.Create();
__directoryInfo.Attributes = FileAttributes.Hidden;
}
FileInfo __fileInfo = new FileInfo(string.Format("{0}/{1}", __directoryInfo.FullName, this._source.Segments[this._source.Segments.Length - 1]));
if (!_cache.ContainsKey(this._source))
{
if (!__fileInfo.Exists)
{
int __byte;
FileStream stream = new FileStream(__fileInfo.FullName, FileMode.CreateNew);
while ((__byte = __resourceStream.Stream.ReadByte()) != -1)
{
stream.WriteByte((byte)__byte);
}
stream.Close();
__resourceStream.Stream.Close();
}
_cache.Add(this._source, __fileInfo);
return new Uri(__fileInfo.FullName);
}
return new Uri(_cache[this._source].FullName);
}
}
3D Decoration
Expander controls are ready to be consumed by the application as in 2D as 3D. The class Viewport2DVisual3D plays its role of a 3D decorator for them. The object is easy to use because it is just another type of child of the Viewport3D object like the Model3D class, for example. The Geometry3D needs to be defined and in my case a form of plane will be sufficient. Then I define the control as the visual element and I just specify the texture as an interactive item.
<Viewport3D ClipToBounds="False" Width="168" Height="210">
<Viewport3D.Camera>
<PerspectiveCamera FarPlaneDistance="10" LookDirection="0,0,-1" UpDirection="0,1,0" NearPlaneDistance="1" Position="0,0,1.9" FieldOfView="45" />
</Viewport3D.Camera>
<Viewport2DVisual3D>
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 1,1,0" TextureCoordinates="0,0 0,1 1,1 1,0" TriangleIndices="0,1,2 2,3,0" />
</Viewport2DVisual3D.Geometry>
<Viewport2DVisual3D.Material>
<DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True" />
</Viewport2DVisual3D.Material>
<Viewport2DVisual3D.Transform>
<Transform3DGroup>
<ScaleTransform3D ScaleX="0.8" ScaleY="1" ScaleZ="1" />
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Angle="18" Axis="1 0 0"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="-0.25" OffsetY="-0.045" OffsetZ="0.24" />
</Transform3DGroup>
</Viewport2DVisual3D.Transform>
<lc:RDExpander Margin="2" ExpandDirection="Up" MouseEnter="_ExpanderEnter">
<lc:RDExpander.Header>
<lc:MatrixTextBlock Text="Enter Thor's matrix" ImmediateWrite="False" Width="128" Height="Auto" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="WrapWithOverflow" />
</lc:RDExpander.Header>
<Grid>
<StackPanel Width="130" Height="70">
<TextBlock Margin="2">
<Hyperlink NavigateUri="http://blog.rioterdecker.net/blogs/valhalla">Thor's blog</Hyperlink>
</TextBlock>
<TextBlock Margin="2">
<Hyperlink NavigateUri="http://www.netfxfactory.org">NetFXFactory</Hyperlink>
</TextBlock>
<TextBlock Margin="2">
<Hyperlink NavigateUri="http://www.mexedge.com">MexEdge</Hyperlink>
</TextBlock>
</StackPanel>
</Grid>
</lc:RDExpander>
</Viewport2DVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<AmbientLight Color="#333333" />
<DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
<DirectionalLight Color="#FFFFFF" Direction="0.612372,-0.5,-0.612372" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
That's all for you guys.
I love WPF! It is merely limit...as your imagination!