Welcome to RioterDeckers' HeadQuarter Sign in | Join | Help

Viewport2DVisual3D: Matrix Expander on WPF 3.5

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!

posted by Chaz | 0 Comments
Filed Under:

GuidMarket SP1 - Next step into SilverLight

It's the tendency so why not a first service pack for the GuidMarket ?
More seriously, my first post about Silverlight (aka WPF/e) is especially dedicated to the technology evaluation that means the javascript code used to handle UI objects stay very "old school".

It's now interesting to provide a more flexible and improved code to extend the app with more objects and more features and thus to try to understand how to use this technology as well.

Download

 

What I modified


The first modification concerns the batch loading of the xaml streams which misses stability. So I wrote my own downloader javascript object that encapsulates the Downloader wpf/e object. I added an application object to easily extract the host object and the main canvas. The object enables me to bufferize the streams and the data that improves the synchronization between requests. I flushed the buffers within the main canvas when the loaded event is fired.

The second important modification concerns the "vista like" window loading. I added a WindowObject javascript object that manage the xaml on-the-fly creation and expose properties and methods to interact with the window. As the first version does not allow to create several windows on a same template, this version enables to create generate more than one window with different contents.

function WindowObject(name, title, width, height, parentObject, contentUri, callback)
{
  var
_width = width;
  var
_height = height;
  var
_title = title;
  var
_name = name;
  var
_parent = parentObject;
  var
_left = (window.screen.width - _width) / 2;
  var
_top = (window.screen.height - _height) / 2;
  var
_contentUri = contentUri;
  var
_callback = callback;
 
  this.Show = function
()
  {
    _application.MainWindow = this
;
    var __downloader = new
Downloader();
    var __loadEventArgs = new LoadEventArgs("windowtemplate.xaml", this._OnLoaded, null
)
    __downloader.LoopBack(__loadEventArgs);
  }

  this._OnLoaded = function
(args)
  {
    if(_parent == null) return
;
    var
__host = _application.GetHost();
    var
__child = __host.CreateFromXaml(args);

    _parent.children.add(__child);
    _parent.findName(Constants.WINDOW_TITLE).Text = _title;
    _parent.findName(Constants.WINDOW_ELEMENT)["Canvas.Top"
] = _top;
    _parent.findName(Constants.WINDOW_ELEMENT)["Canvas.Left"
] = _left;
    _parent.findName(Constants.OUTER_BORDER).Width = _parent.findName(Constants.INTER_BORDER).Width = _parent.findName(Constants.INNER_BORDER).Width = _width;
    _parent.findName(Constants.OUTER_BORDER).Height = _parent.findName(Constants.INTER_BORDER).Height = _parent.findName(Constants.INNER_BORDER).Height = _height;
    _parent.findName(Constants.BUTTON_CLOSE)["Canvas.Left"
] = _width - 51;
    _parent.findName(Constants.WINDOW_CONTENT).Width = (_width - 24);
    _parent.findName(Constants.WINDOW_CONTENT).Height = (_height - 37);
    _parent.findName(Constants.INNER_CONTENT_BORDER).Width = (_width - 14);
    _parent.findName(Constants.INNER_CONTENT_BORDER).Height = (_height - 37);
    _parent.findName(Constants.OUTER_CONTENT_BORDER).Width = (_width - 12);
    _parent.findName(Constants.OUTER_CONTENT_BORDER).Height = (_height - 35);
    _parent.findName(Constants.BUTTON_OK)["Canvas.Top"
] = (_height - 73);
    _parent.findName(Constants.BUTTON_OK)["Canvas.Left"
] = (_width - 115);

    _LoadContent(_contentUri);
  }

  this.Close = function
()
  {
    var
__window = _parent.findName(Constants.WINDOW);
    _parent.children.remove(__window);
    _application.MainWindow = null
;
  }

  var _LoadContent = function
(uri)
  {
    var __downloader = new
Downloader();
    var __loadEventArgs = new LoadEventArgs(uri, _OnContentLoaded, null
);
    __downloader.LoopBack(__loadEventArgs);
  }

  var _OnContentLoaded = function
(args)
  {
    var
__content = _application.GetHost().CreateFromXaml(args);
    _parent.findName(Constants.WINDOW_CONTENT).children.Add(__content);
    if(_callback != null
) _callback();
  }
}

 

Controls and ControlTemplates


I already said that control and controltemplate as we know in WPF do not exist in Silverlight. However, it's possible to have a similar feature by defining javascript objects which provide common characteristics and behaviors for UI objects. The graphical rendering can be loaded dynamically and synchronized by using xaml fragments loaded by the Downloader object.

The code bellow illustrates the definition of a ButtonBase control (JSON) which is rendered as a "vista like" button (without visual effects):

ButtonBase = function()
{
}
ButtonBase.prototype =
{
 
_content : null
,
  _height :
null
,
  _width :
null
,
  _left :
null
,
  _top :
null
,
  _visual :
null
,

 
initialize: function
initialize()
  {
   
var __fragment = '<Canvas Name="_buttonCanvas" Canvas.Top="'+ this.Top +'" Canvas.Left="' + this.Left + '" MouseLeftButtonUp="BLOCKED SCRIPTonclick">'
;
          __fragment +=
'<Rectangle Width="' + this.Width + '" Height="' + this.Height + '" RadiusX="2" RadiusY="2" Stroke="#A6A6A6" StrokeThickness="1">'
;
          __fragment +=
'<Rectangle.Fill>'
;
          __fragment +=
'<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">'
;
          __fragment +=
'<GradientStop Offset="0.0" Color="#FFF9F9F9" />'
;
          __fragment +=
'<GradientStop Offset="0.5" Color="#FFF3F3F3" />'
;
          __fragment +=
'<GradientStop Offset="0.5" Color="#FFE8E8E8" />'
;
         
__fragment += '<GradientStop Offset="1.0" Color="#FFD5D5D5" />'
;
          __fragment +=
'</LinearGradientBrush>'
;
          __fragment +=
'</Rectangle.Fill>'
;
          __fragment +=
'</Rectangle>'
;
          __fragment +=
'<Rectangle Width="' + this.Width + '" Height="' + this.Height + '" RadiusX="2" RadiusY="2" Stroke="#4AB2D9" StrokeThickness="1" />'
;
          __fragment +=
'<TextBlock Canvas.Top="7" Canvas.Left="37" FontFamily="Segoe UI, Verdana, Arial" FontSize="11" Foreground="Black" Text="' + this.Content + '" />'
;
          __fragment +=
'</Canvas>'
;

    var
__host = document.getElementById('wpfeControl1');
    _visual = __host.CreateFromXaml(__fragment);
  },

  attach :
function
attach()
  {
   
this
.initialize();
   
var __host = document.getElementById('wpfeControl1'
);
   
var __content = __host.findName("_content"
);
    __content.children.add(_visual);
  },

 
get_Top: function get_Top() { return
_top; },
  set_Top:
function
set_Top(value) { _top = value; },

  get_Left :
function get_Left() { return
_left; },
  set_Left :
function
set_Left(value) { _left = value; },

  get_Width :
function get_Width() { return
_width; },
  set_Width :
function
set_Width(value) { _width = value; },

  get_Height :
function get_Height() { return
_height; },
  set_Height :
function
set_Height(value) { _height = value; },

  get_Content :
function get_Content() { return
_content; },
  set_Content :
function
set_Content(value) { _content = value; }
}

The code of the object have to change the object itself but also the dependent Silverlight objects that are generated. However there is an important constraint because the Silverlight objects are not available by code until they are added to their parent. It's possible to either read xaml fragment such as above or load xaml from a file as the WindowObject of the GuidMarket application. Indeed, the .xaml files could be regarded as much templates.

 

The code below shows how to create a new instance of our ButtonBase and how to add it to a parent object as a Canvas. In this sample I decided to use an attach method to render the control but in a real application, you should have to create a custom object manager to add and retrieve the controls and to release resources when they are removed from their parents.

var _buttonBase = new ButtonBase();
_buttonBase.Content = "OK";
_buttonBase.Width = 100;
_buttonBase.Height = 30;
_buttonBase.Top = 10;
_buttonBase.Left = 10;
_buttonBase.attach();

Doubtful programmability


For the time being, I think Silverlight is not very flexible. A dynamic application needs too many asynchronous loadings. I had many problems between the asynchronous loading with the Atlas ScriptManager and the WPF/e Downloader object (and I don't speak about the permanent 'unexpected error' and other 'unknown exception' that everytime explode directly on your face).

I think the first real application need to be designed with more common controls and by creating custom controls. Custom controls can be developed but with too many code, the application needs a mediator object to handle them efficiently, to maintain the correct synchronization between WPF/e objects and javascript objects and to be able to raise custom events.

We may hope an efficient coupling between a more extended object model and Script# with the next CTP. While waiting, read the Nikhil Kothari's blog for more information...

 

Download new version

posted by Chaz | 0 Comments
Filed Under:

One step beyond Silverlight

After WPF, I said myself 'waoh!', that must be huge to provide the same graphical quality for the web apps, the same flexibility. I just would like to go on throwing the new type of user xp in the user's face ! It's however necessary to keep in mind some design considerations. Whereas the .xbap pages are fully written on WPF and CSharp, the Silverlight plug-ins are clother to applets which enhance classical web pages. Let's discover my first Silverlight app: The GuidMarket is born.

[They are fresh my Guids! They are fresh!]

The application concept


The application is designed with a main xaml page and a whole of pages that are dynamically loaded according to the user actions such as a click on a Guid package or the load of the 'vista like' window. The business functionalities, extremely simple obviously, are provided by a local xml web service. To date, the plateform does not allow direct access to external features, that means one ore more local web services must be a bridge by consuming the external resources and send back streams to the client app.
However another way can be used: the Silverlight downloader object that enables sending requests to web services whether they accept using the GET method. The problem is to parse the xml stream returned that is 'normally' done by using the Atlas ScriptManager web control. The XmlHttpRequest should be an alternative to solve dynamic loadings...

function LoadXamlStream(uriStream, callback, args)
{
 
var __downloader = _host.createobject("downloader");
  __downloader.completed = "BLOCKED SCRIPTOnComplete"
;

  if(callback != null)
  {
    _callback = callback;
    _callbackArgs = args;
  }

  __downloader.open(
"GET", uriStream, true);
  __downloader.send();
}

function OnCompleted(sender, args)
{
 
var __content = _host.findName("_content");
 
var __canvas = _host.CreateFromXaml(sender.responseText);
  __content.children.Add(__canvas);
 
 
if(_callback != null) _callback(_callbackArgs);
}

Technical considerations


After my multiple investigations on WPF, I was very astonished about the features provided by the Silverlight plateform. Only one layout type, the Canvas element; the common controls such as buttons, hyperlinks, checkboxes, radio buttons do not exist. The least button needs designer qualities to avoid being grotesque. The absence of ControlTemplate elements also needs to reconsider how to design applications when you come from WPF.

Other important features are missing as the BitmapEffects elements that penalize the design of the apps and cause the proliferation of pictures what is desappointing according to me. I suppose the missing features will be caught up with the next CTP...I tried with the GuidMarket app to minimize the using of pictures and I preferred to use the  provided routines massively. In addition with the background elements, only the Purchase button and the button that closes the window are designed with pictures.

Atlasians, win a free platinum Guid


The most important difficulty for the application design is the dynamic loading of the xaml stream using the downloader object and the data loading via the xml web service where the streams are returned asynchronously.
The loading cycle starts with the main xaml page following by the 5 next pages that I synchronise by multiple callbacks. This manual synchronisation is very tiresome and avoid losing streams of the first four pages !! Should the combinaison of Atlas and Silverlight enable dynamic loading of streams in a more flexible way ?

To go further


The most plug-ins developed on the plateform use xml web services to provide business mechanisms and functionalities. The idea is that the web services could become providers of xaml fragments in order to enable the client apps to load graphical elements dynamically, to respond some user actions without refreshing the page.

So, does this kind of web service replace the missing ControlTemplate elements in another way ?!?

Download the GuidMarket sources (Silverlight feb CTP)

 

On an original idea of Raskal (lol)
On a concept of WoZoI

 

posted by Chaz | 4 Comments
Filed Under: ,

CodeMaster breaking news

Hi there,

It's done, the party is over. Right now, you can find out some of the materials used for the Massaï project presentation. I suggest you to enjoy our huge project with a video that sums up the most features.
WoZoI will publish the post-production movie soon on his blog.

In waiting for the verdict of the judges...

The Massaï project

UI enhancement

How to create multi-window and multi-layout app

I AM A RIOTERDECKER

posted by Chaz | 0 Comments

There can be only one...Code Master

I've just received confirmation by the code master team that the Massaï project and so I, continue the challenge. I also could find out countries of other challengers and I'm very proud to be the last frenchy geek always on the battle field !!

The next rendez-vous is november 9 for the live presentation. After that, be sure you could read my ROI on WPF with some original concepts based on the entire work for this very attractive project. Stay tuned...

Darth Chaz.

posted by Chaz | 0 Comments
Filed Under:

100% Warrior

Hi there everyone.
I have just received a great breaking news from the CodeMaster Challenge admin. I'M A FINALIST !!! Yeah !! It's huge !! The Massai project is always in the place.
So you will have to wait for reading next articles about WPF because I need to prepare the next step of the challenge: the presentation of the application to be one of the winners.

You will have the results soon. The Massai project continues to make noise.
So let's go to the final step...

posted by Chaz | 0 Comments
Filed Under:

CodeMaster Challenge breaking news

You may wonder if I'm dead or not. Hey not yet!
I'm boosting the conception and realization of the Massaï project and it's true I have not much time to publish articles on my WPF investigations. So it's done! I've just submitted the description and features of the Massai projet for the CodeMaster Challenge, even though the code is not entirely done.
Some of you wonder what is this projet. For now, in a few words I can say that this project is a new way to manage, share and organize role playing games in various universes. Let's hope the Massai project will be a great challenger and also a finalist...
Another screenshot has just been published on my massaï album

posted by Chaz | 0 Comments
Filed Under:

Enter the expander

In waiting for the next post about the WinFX wizard framework (stay calm, it's in progress), I decided to post a simple entry about a piece of my work on the Massaï project: a custom templated Expander control. But it would be too easy to explain how to customize a lonely poor expander. No! I cannot do that, you know ;)
So here is a custom expander in a Matrix theme.



[Figure 1. Expander everywhere]

First, the goal is to template an expander control to apply particular visual appearance but in keeping the control's features. It's not so easy because once templated, the expand/collapse functionality is not directly available anymore. Just have a look on a piece of the template:

<ControlTemplate x:Key="RDExpander_Template" TargetType="{x:Type l:RDExpander}">
  <
Grid x:Name="EnterTheMatrix" Width="168" Height="109"
>
    <
ContentPresenter x:Name="_expanderContent" Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}"
>
      <
ContentPresenter.RenderTransform
>
        <
TranslateTransform X="0" Y="0"
/>
      </
ContentPresenter.RenderTransform
>
    </
ContentPresenter
>
    <
Image Source="Images/MatrixScreen.png" Width="168" Height="109"
/>
    <
Grid Margin="4,2,0,0"
>
      <
ContentPresenter x:Name="_header" Content="{Binding Path=Header, RelativeSource={RelativeSource TemplatedParent}}"
/>
      ...
    </Grid
>
  </
Grid
>
  ...
</ControlTemplate>

Until now, no problem...
I used two ContentPresenter to display the header and the content of the expander.
Just have a look on the final used expander:

<l:RDExpander Margin="2" MouseEnter="_ExpanderEnter">
 
<
l:RDExpander.Header>
   
<
l:MatrixTextBlock Text="Enter my matrix" ImmediateWrite="False" Width="128" Height="Auto" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="WrapWithOverflow" />
 
</
l:RDExpander.Header>
 
<
Grid>
   
<
Image Source="Images/MatrixFrame.png" Width="160" Height="101" />
   
<
StackPanel Width="130" Height="70">
     
<
TextBlock Margin="2">
       
<
Hyperlink x:Name="_chazHL" NavigateUri="http://blog.rioterdecker.net/blogs/chaz">Chaz's blog</Hyperlink>
     
</
TextBlock>
   
</StackPanel>
 
</
Grid>
</
l:RDExpander>

If you would try this code, the expander neither expands nor collapsed its content by clicking on the header. So I decided to add a simple Button control in the template. There is another problem. How to bound the click event on the expand/collapse functionality ? One solution consists in adding a RoutedCommand to my custom expander and bound it with the Button:

public partial class RDExpander : Expander
{
 
public static readonly RoutedCommand ExpandOrCollapseCommand;

  static RDExpander()
 
{
   
RDExpander.ExpandOrCollapseCommand = new RoutedCommand("ExpandOrCollapse", typeof(RDExpander));
   
CommandManager.RegisterClassCommandBinding(typeof(RDExpander), new CommandBinding(ExpandOrCollapseCommand, new ExecutedRoutedEventHandler(OnExecuteCommand), new CanExecuteRoutedEventHandler(OnQueryExecuteCommand)));
 
}

  private static void OnExecuteCommand(object target, ExecutedRoutedEventArgs e)
 
{
   
RDExpander __expander = (RDExpander)target;
   
if (e.Command == RDExpander.ExpandOrCollapseCommand)
   
{
     
__expander.IsExpanded = !__expander.IsExpanded;
   
}
 
}
 
private static void OnQueryExecuteCommand(object target, CanExecuteRoutedEventArgs e)
 
{
   
e.CanExecute =
true;
 
}
}

...
<ContentPresenter x:Name="_header" Content="{Binding Path=Header, Rela