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, RelativeSource={RelativeSource TemplatedParent}}" />
<
Button x:Name="_matrix" Style="{StaticResource MediaButtonStyle}" Command="l:RDExpander.ExpandOrCollapseCommand" />
...

The expander provides two routed events, Expanded and Collapsed which are raised when the IsExpanded property has changed. So I just need to add triggers on these events to animate the content. To allow the content to be expanded up, I add another RoutedEvent which is raised when the ExpandDirection property is set to Up:

protected override void OnExpanded()
{
 
switch(base.ExpandDirection)
 
{
   
case ExpandDirection.Down:
     
base.OnExpanded();
     
break;

    case ExpandDirection.Up:
     
RoutedEventArgs __args = new RoutedEventArgs(ExpandedUpEvent);
     
base.RaiseEvent(__args);
     
break;
 
}
}

For this version, I decided that the control would not support left and right directions. But you can easily add these functionalities...

In the codes above, you certainly noticed that I used another custom control: MatrixTextBlock. This control derives from a simple TextBlock and display text as a typewriter. In the next post, I just extract this control for you. After that, I will add scroll bars in the content of the expander and will change their styles too. As I used to say, download it and eat your ice cream :) !!

Download the sources
Download the binaries
If you have not the right fonts (Agency FB and OCR Extended), download them now.

(WinFX Feb. CTP)


posted by Chaz | 0 Comments
Filed Under:

100% Geek


You certainly know that Microsoft launched a new challenge to celebrate the close release of Windows Vista: the Code Master Challenge. A few weeks ago I decided to compete for the title with the first product of the RioterDecker team code named Massaï.
This project is built on intensive UI experience. I cannot tell you more for now but be sure that the Massaï project will make speak about him. The shock will be brutal and it will not leave any survivor !!
posted by Chaz | 0 Comments
Filed Under:

WinFX Wizard announcement

With this post, I just want to present you a new concept to design wizards. Everyone knows wizards and massively uses them because they make our "e-life" easier but in the most of time they are not very fun. Always next...next..next...finish without any attractive features. So I'm pleased to show the first works about our future Wizard Framework on WinFX.
The figure below shows you a preview of the default style applied to a sample created with the WizardFramework.



[Figure 1. Wizard Preview]

How will it work ? The UI is built on WPF (of course) but this time we're trying to cross it with Windows Workflow Foundation (WF). Indeed, you will be able to define your activities based on the WizardFramework's ones. But I cannot say anymore for now. WF is the Raskal's favorite job and he will explain the interaction between the two technologies in the next post...

For the time being, appreciate the wizard in action:
The picture above is good but a video is better. In this first video, the wizard define 4 steps, I first navigate to the next steps and also to a previous step in order to see all the animations. So do not wait anymore and pump up the volume...

Download the video (medium quality because of the size)
Download the codec if necessary (XviD MPEG-4)


posted by Chaz | 0 Comments
Filed Under:

How to use DataBinding with the EllipticControl

It's here! The new version of the elliptic control is now available!
As I promised a few months ago, I improved the control by adding databinding capabilities to add items around the ellipse. I also changed some code to correct some bugs and improve the animation of items.
Even though I had some bugs from the CTP when I attempted to manipulate the VisualTree, I found another strategy which does not affect the performance and the reliability of the control.



[Figure 1. The new version]

So what did I modify in the code:

  1. The EllipticItemSelectorControl derived from ItemsControl, implements and overrides some methods.
  2. The EllipticItem derived from ContentControl in order to be able to receive the data.
  3. The Viewbox disappeared and has been replaced by a scale transformation. The timeline using key frames has been adapted.
  4. The SelectionChanged event now publishes 2 properties, OldValue and NewValue that are the unselected and the selected item.
  5. The windows application that consumes the control defines the data in an Xml format and contains the DataTemplate to apply to the items.

The Data

The sample contains all you need to know about the RioterDeckers ;).
I choose to use a simple XmlDataProvider as describes below:

<XmlDataProvider x:Key="RDDataSource" XPath="/RioterDeckers/Geeks">
 
<
x:XData>
   
<
RioterDeckers xmlns="">
     
<
Geeks>
       
<
RioterDecker Name="AvalonBoy" Image="../images/avalonboy.rd.release.png" Drilling="18" Analysis="12" DataBlaster="15" Early="16" UI="17" Project="12" Business="12" Phrase="'Put this in your head.'" />
       
<
RioterDecker Name="Chaz" Image="../images/chaz.rd.release.png" Drilling="16" Analysis="14" DataBlaster="15" Early="15" UI="18" Project="15" Business="12" Phrase="'I kicked the first, I smashed the second! And I swam!'" />
       
<
RioterDecker Name="Raskal" Image="../images/raskal.rd.release.png" Drilling="15" Analysis="14" DataBlaster="15" Early="15" UI="12" Project="11" Business="18" Phrase="'It's integrated to GDPML. Neness!'" />
       
<
RioterDecker Name="Thor" Image="../images/thor.rd.release.png" Drilling="15" Analysis="12" DataBlaster="17" Early="18" UI="15" Project="13" Business="12" Phrase="'Who is OK for a bullshit bingo ?'" />
       
<
RioterDecker Name="WoZoI" Image="../images/wozoi.rd.release.png" Drilling="13" Analysis="18" DataBlaster="11" Early="14" UI="11" Project="15" Business="12" Phrase="'Vous allez rire, il n'y a pas de serrure (in french in the text).'" />
     
</
Geeks>
   
</
RioterDeckers>
 
</
x:XData>
</
XmlDataProvider>


How to consume the elliptic control

As any type of ItemsControl, you must specify the data source to apply and the template for each item added to the control.
The other properties are the same than the first version of the control. As you can see below, I simply bind to my data provider and I define which is the template to apply to each item. Notice that I also intercept the Click event and the (new version of) SelectionChanged event.
This time, that's all you need to add the elliptic control.

<cc:EllipticItemSelector x:Name="_selector" ItemWidth="90" ItemHeight="90" EllipticWidth="175" EllipticHeight="70" Angle="25" Canvas.Top="300" Canvas.Left="225" SelectionChanged="_SelectionChanged" Click="_SelectorClick" ItemsSource="{Binding Source={StaticResource RDDataSource}, XPath=RioterDecker}" ItemTemplate="{StaticResource RD_DataTemplate}" />

The DataTemplate below defines the visual appearance and the behavior of each item added to the control:

<DataTemplate x:Key="RD_DataTemplate">
 
<
StackPanel Width="{Binding Path=Width, RelativeSource={RelativeSource TemplatedParent}}" Height="{Binding Path=Height, RelativeSource={RelativeSource TemplatedParent}}">
   
<
Image Source="{Binding XPath=@Image}" Width="70" Height="70" />
   
<
Grid>
     
<
TextBlock x:Name="PART_Text" Text="{Binding XPath=@Name}" Style="{StaticResource RDText_Style}" />
   
</
Grid>
 
</
StackPanel>
 
<
DataTemplate.Triggers>
   
<
EventTrigger RoutedEvent="cc:EllipticItem.MouseEnter">
     
<
EventTrigger.Actions>
       
<
BeginStoryboard Storyboard="{StaticResource RDTextAnimation_Enter}" />
      
</
EventTrigger.Actions>
   
</
EventTrigger>
   
<
EventTrigger RoutedEvent="cc:EllipticItem.MouseLeave">
     
<
EventTrigger.Actions>
       
<
BeginStoryboard Storyboard="{StaticResource RDTextAnimation_Leave}" />
     
</
EventTrigger.Actions>
   
</
EventTrigger>
 
</
DataTemplate.Triggers>
</
DataTemplate>


The new EllipticItemSelector

To enable adding items by data binding, the control must derive from ItemsControl which provides the ItemsSource and the ItemTemplate properties.
I need to ensure two things: each created item is of EllipticItem type and determine whether each item is its own container.
I also modified others methods which have no responsibility in data binding so download the source to have the entire modified code.

public partial class EllipticItemSelector : ItemsControl
{
 
protected override DependencyObject GetContainerForItemOverride(object item)
 
{
   
EllipticItem __ellipticItem = new EllipticItem();
   
__ellipticItem.MouseLeftButtonDown +=
new System.Windows.Input.MouseButtonEventHandler(__ellipticItem_MouseLeftButtonDown);

    return __ellipticItem;
 
}
 
protected override bool IsItemItsOwnContainerOverride(object item)
 
{
   
return (item is EllipticItem);
 
}
}

Remember that in the first version, I used a Canvas to position the items. In this version, I always used the Canvas because I don't choose the translate transformation to move items because using the x and y coordinates instead of transformation preserve resources. In this version, the canvas is defined as a ControlTemplate within the control.


The new EllipticItem

To enable storing data of the item, the new control derives from ContentControl which provides the Content property, necessary to retrieve data.
I replaced the initial viewbox, attached during the life cycle of the item by a scale transformation which improves the quality of the animation (even though you are like me, with a poor video card).


The bonus

In this version of the sample, I decided to get benefits of the data bound to the control to display information about the RioterDeckers.
So I created a control with all the needed DependencyProperty to receive the appropriate data:

<Grid xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
  xmlns:x
=http://schemas.microsoft.com/winfx/2006/xaml

  x:Class
="RD.EllipticSample.RDAbilityControl"
 
Name="_mainControl" Opacity="0" Margin="20" Width="225">
 
<
Grid.Resources>
   
<
Style x:Key="RDText_Style" TargetType="{x:Type TextBlock}">
     
<
Setter Property="FontFamily" Value="Segoe UI" />
     
<
Setter Property="FontSize" Value="9" />
     
<
Setter Property="Foreground" Value="White" />
     
<
Setter Property="HorizontalAlignment" Value="Center" />
     
<
Setter Property="Opacity" Value="0" />
     
<
Setter Property="TextBlock.BitmapEffect">
       
<
Setter.Value>
         
<
DropShadowBitmapEffect Color="Black" Direction="0" Opacity="0.4" ShadowDepth="2" Softness="0.2" />
       
</
Setter.Value>
     
</
Setter>
   
</
Style>
   
<
Style x:Key="{x:Type TextBlock}" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource RDText_Style}">
     
<
Setter Property="Opacity" Value="1" />
     
<
Setter Property="FontSize" Value="12" />
     
<
Setter Property="HorizontalAlignment" Value="Left" />
   
</
Style>
   
   
<
Storyboard x:Key="SB_Open">
     
<
DoubleAnimation From="0" To="1" Duration="0:0:0.2" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="_mainControl" />
   
</
Storyboard>
   
<
Storyboard x:Key="SB_Close">
     
<
DoubleAnimation From="1" To="0" Duration="0:0:0.2" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="_mainControl" />
   
</
Storyboard>
 
</
Grid.Resources>

  <Grid.DataContext>
   
<
Binding ElementName="_mainControl" />
 
</
Grid.DataContext>
 
<
Grid.ColumnDefinitions>
   
<
ColumnDefinition />
   
<
ColumnDefinition />
 
</
Grid.ColumnDefinitions>
 
<
Grid.RowDefinitions>
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
   
<
RowDefinition />
 
</
Grid.RowDefinitions>
 
<
TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" FontSize="14" Margin="0,0,0,10" HorizontalAlignment="Center">
   
<
TextBlock Text="The abilities of " />
   
<
TextBlock Text="{Binding Path=RDName}" />
 
</
TextBlock>
 
<
TextBlock Grid.Column="0" Grid.Row="1" Text="DotNet drilling" />
 
<
TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Path=Drilling}" HorizontalAlignment="Right" />
 
<
TextBlock Grid.Column="0" Grid.Row="2" Text="Analysis gathering" />
 
<
TextBlock Grid.Column="1" Grid.Row="2" Text="{Binding Path=Analysis}" HorizontalAlignment="Right" />
 
<
TextBlock Grid.Column="0" Grid.Row="3" Text="DataBlaster" />
 
<
TextBlock Grid.Column="1" Grid.Row="3" Text="{Binding Path=DataBlaster}" HorizontalAlignment="Right" />
 
<
TextBlock Grid.Column="0" Grid.Row="4" Text="Early adopter" />
 
<
TextBlock Grid.Column="1" Grid.Row="4" Text="{Binding Path=Early}" HorizontalAlignment="Right" />
 
<
TextBlock Grid.Column="0" Grid.Row="5" Text="UI Arts" />
 
<
TextBlock Grid.Column="1" Grid.Row="5" Text="{Binding Path=UI}" HorizontalAlignment="Right" />
 
<
TextBlock Grid.Column="0" Grid.Row="6" Text="Project handling" />
 
<
TextBlock Grid.Column="1" Grid.Row="6" Text="{Binding Path=Project}" HorizontalAlignment="Right" />
 
<
TextBlock Grid.Column="0" Grid.Row="7" Text="Business opportunist" />
 
<
TextBlock Grid.Column="1" Grid.Row="7" Text="{Binding Path=Business}" HorizontalAlignment="Right" />
 
<
TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="8" Margin="0,10,0,0" Text="Master in technologeek" />
 
<
TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="9" Margin="0,20,0,0" HorizontalAlignment="Center" FontStyle="Italic" TextWrapping="Wrap" Text="{Binding Path=Phrase}" />
</
Grid>

I added a method in the window to set data to the properties. Keep in mind that the items of the elliptic control provides data to be displayed. I retrieve data within the Content property when the event SelectionChanged is raised. Don't forget that the content of the item is an XmlElement because I used data in an Xml format:

private void _SelectionChanged(object sender, EllipticSelectionChangedEventArgs e)
{
 
EllipticItem __item = e.NewItem;
 
_BuildAbilityControl(__item);
 
_rdAbilityControl.OpenScene();
}
private void _BuildAbilityControl(EllipticItem item)
{
 
XmlElement __element = (XmlElement)item.Content;
 
  _rdAbilityControl.SetValue(
RDAbilityControl.RDNameProperty, __element.Attributes["Name"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.DrillingProperty, __element.Attributes["Drilling"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.AnalysisProperty, __element.Attributes["Analysis"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.DataBlasterProperty, __element.Attributes["DataBlaster"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.EarlyProperty, __element.Attributes["Early"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.UIProperty, __element.Attributes["UI"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.ProjectProperty, __element.Attributes["Project"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.BusinessProperty, __element.Attributes["Business"].Value);
 
_rdAbilityControl.SetValue(
RDAbilityControl.PhraseProperty, __element.Attributes["Phrase"].Value);
}


Finally

With the next CTP, I will upgrade this version of the control and I will provide another functionality: determine the minimum size and the maximum size of items used during the scale transformation to simulate the 3-D depth.

Download the sources
Download the binaries

(WinFx Feb. CTP)


posted by Chaz | 0 Comments
Filed Under:

Introducing RoutedThemeDictionary, a new ThemeDictionaryExtension

With this post, I'm pleased to present a new concept and a new use of the ResourceDictionary object to apply themes. In my preceeding post, I explained how to apply themes in your applications in a industrial process. Now I gonna show you how to automatically apply a theme when the system theme changes. This is the idea of the concept : automatically route a specific theme when the user applies another theme on Windows XP, so you can easily synchronize your application with XP themes and change the appearance by doing...nothing.

Just apply this lonely line of code in your application:

RoutedThemeManager.ApplyRoutedDictionary(new Uri("RD.RoutedTheme.Sample.Luna", UriKind.Relative));

Consider that RD.RoutedTheme.Sample.Luna is the assembly that contains the dictionaries of themes to apply according to the applied system theme. So you do not need to set a ResourceDictionary to your application anymore.

See the RoutedTheme in action (needs Flash player)


The concept

In order to apply ResourceDictionary objects that must be loaded according to the current system theme, I declare a dictionary with this particular name: themename.themecolor.xaml
So I create:

  • luna.normalcolor.xaml (for the Default blue theme)
  • luna.homestead.xaml (for the Olive theme)
  • luna.metallic.xaml (for the Silver theme)

Go and see this post of Avalonboy who extracted system themes for you...

In each dictionary, I just add my own ThemeDictionaryExtension named RoutedThemeExtension that always specify the assembly name of the dictionary to merge and the other ResourceDictionary for the routing.
So I add:

  • Resource_LunaNormal.xaml (routed dictionary for the Default blue theme)
  • Resource_LunaHomestead.xaml (routed dictionary for the Olive theme)
  • Resource_LunaMetallic.xaml (routed dictionary for the silver theme)

I also create a RoutedThemeManager to use instead of the classic Application.LoadComponent syntaxe to automatically synchronize routed themes with XP. Wow! I can hear you... How can I know what theme is in use ? That's right, I need a wrapper to call UxTheme APIs. You will be able to view the code of my UxThemeWrapper in the sources of the sample.
Note that to illustrate the concept, I resused the themes already used in my first post about themes.


The RoutedThemeExtension

This new extension is destined to be used within a ResourceDictionary as a source and has several roles in the process. It enables to load appropriate RoutedDictionary that contains the specific theme to apply, it detects when the theme has changed and merge the routed dictionary with the system theme to allow overriding it.
To be able to load the system dictionary, WPF builds the Uri by joining the assembly name with the name and the location of the ResourceDictionary, e.g. to load the luna.normalcolor theme, the initial dictionary must be named as luna.normalcolor.xaml and the ThemeDictionaryExtension must specify '/PresentationFramework.Luna;v3.0.51116.0;31bf3856ad364e35' as assembly name. Don't forget the dictionary must be placed in a themes folder.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 
xmlns:cc="clr-namespace:RD.RoutedTheme;assembly=RD.RoutedTheme"
>
 
<
ResourceDictionary.Source>
   
<
cc:RoutedTheme AssemblyName="/PresentationFramework.Luna;v3.0.51116.0;31bf3856ad364e35" RoutedDictionary="RD.RoutedTheme.Sample.Luna;;;component\themes/Resource_LunaNormal.baml" />
  
</
ResourceDictionary.Source>
</
ResourceDictionary>

Thus I defined a RoutedThemeExtension derived from the ThemeDictionaryExtension and add the RoutedDictionary property to load the specific dictionary when the system theme is loaded.

public sealed class RoutedThemeExtension : ThemeDictionaryExtension
{
 
private Uri _routedDictionaryUri;

 
public RoutedThemeExtension()
 
{
 
}

  public Uri RoutedDictionary
 
{
   
get { return _routedDictionaryUri; }
   
set { _routedDictionaryUri = value; }
 
}

  public override object ProvideValue(object targetObject, object targetProperty)
 
{
   
Uri __uri = this.RoutedDictionary;
   
if (__uri != null)
   
{
     
ResourceDictionary __dictionary = RoutedThemeManager.LoadRoutedDictionary(__uri);

      string __assemblyName = RoutedThemeManager.CurrentRoutedDictionaryInfo.AssemblyName;
     
MethodInfo __registerInfo = this.GetType().BaseType.GetMethod("Register", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

      if (__registerInfo != null)
     
{
       
__registerInfo.Invoke(
null, new object[] { __dictionary, __assemblyName });
     
}

      Application.Current.Resources = __dictionary;
   
}
   
return base.ProvideValue(targetObject, targetProperty);
 
}
}

I just need to override the ProvideValue method which is called when the ResourceDictionary whom the extension is applied, is loaded.
In this method, I load the routed dictionary and I need to call the Register internal static method of the base class to be notified when the theme has changed.
To remember information about the dictionary to be loaded, I 've just created a private nested class named RoutedDictionaryInfo. As Raskal would say: 'it is not magic, it's just by design...' :)


The RoutedThemeManager

This manager is just a helper to be used instead of any other types of code to apply routed dictionaries.
The only interesting lines of code are listed below:

private static void _TryLoadThemeColor(string themeName, string themeColor, Uri resourceLocator)
{
 
string __path = string.Format(@"/{0};;;component\themes/{1}.{2}.baml", resourceLocator.ToString(), themeName, themeColor);

  ResourceDictionary __dictionary = (ResourceDictionary)Application.LoadComponent(new Uri(__path, UriKind.Relative));
 
RoutedThemeManager.CurrentRoutedDictionaryInfo.DictionaryReference.MergedDictionaries.Add(__dictionary);
}

I know that it is not easy to understand all interactions during the theme change process. So in the sequence diagramm below I tried to expose the messages exchanged between my object and the WPF objects:

 
[Figure 1. RoutedTheme sequence diagramm]


Conclusion

With this post you have seen how to synchronize your own themes with the system themes of Windows XP but you are also able to modify the source code in order to work with other themes and of course to be synchronized on Windows Vista too. So this sample is waiting for only one thing: to be extended!

Download the sources
Download the binaries

(WinFX Feb CTP)


posted by Chaz | 0 Comments
Filed Under:

Create and apply custom themes

Introduction

With WPF, Microsoft introduces one of the most famous mechanism until now reserved almost exclusively for web applications, available for any types of application: UI composed by themes. Indeed, all visual elements can be defined by style and all styles can be regrouped into a specific theme. With extrapolations several themes can also be applied on the same application.


The sample

The sample I provide with this post try to explain how to create several external themes for an application. Why themes are externals ? Because I could integrate my themes in the same assembly but today many profiles participate in the creation of an application. Therefor I would like you to keep in mind that an application is often designed following an industrial process (this is nevertheless the way where we try to go). A development team might create the logic of the application whereas designers imagine the UI by creating several themes to be applied.
Because I didn't want to imagine a complex application to explain the theme concept, I reused the logon application provided by Microsoft with the WinFX SDK. So the sample is composed with 3 parts: an assembly with custom controls and the default theme, another containing the Window application and another one with custom themes.

 

Figure1. Custom themes


Create a generic theme

In the first assembly, I define custom controls for the application. It's not a constraint but it facilitates the design and the theme to be applied. The only thing to do is to override the eventual initial visual styles defined for the base controls:

public class LogonButton : Button
{
 
public
LogonButton()
  {
  }
 
static
LogonButton()
  {
    DefaultStyleKeyProperty.OverrideMetadata(
typeof(LogonButton), new FrameworkPropertyMetadata(typeof(LogonButton
)));
  }
}

Assuming a WPF application considers a ResourceDictionary to be the default theme, I add a ResourceDictionary named generic.xaml. With this name, the dictionary will be automatically applied whether no specific dictionary is set. However considering that the theme is designed in an independent manner, I choose to use the type of the styled object as key instead of a string:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
"
 
xmlns:cc="clr-namespace:RD.CustomThemes.Controls
"
>
  <
Style x:Key="{x:Type cc:BackgroundControl}" TargetType="{x:Type cc:BackgroundControl}"
>
    <
Setter Property="Grid.ColumnSpan" Value="4"
/>
    <
Setter Property="Grid.RowSpan" Value="3"
/>
    <
Setter Property="Template"
>
      <
Setter.Value
>
        <
ControlTemplate TargetType="{x:Type cc:BackgroundControl}"
>
          <
Grid
>
            <
Rectangle
/>
          </
Grid
>
        </
ControlTemplate
>
      </
Setter.Value
>
    </
Setter
>
  </
Style
>
  ...
  <Style x:Key="{x:Type cc:LogonButton}" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type cc:LogonButton}"
>
    <
Setter Property="Width" Value="150"
/>
    <
Setter Property="Grid.Row" Value="2"
/>
  </
Style
>
</ResourceDictionary>

A last thing to do is to verify that a generic theme can be properly applied. Add the ThemeInfo attribute into the AssemblyInfo.cs. Note that with the february CTP, this attribute is added for you in the file.

[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]


Create other specific themes

In another assembly I just create a 'themes' folder, add a reference to the first assembly and define 3 other themes with no constraint naming convention. In each xaml file, I just define the style of each control. The code below comes from one of the 3 resource dictionaries:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
"
 
xmlns:cc="clr-namespace:RD.CustomThemes.Controls;assembly=RD.CustomThemes.Controls
"
>
  <
Style x:Key="{x:Type cc:BackgroundControl}" TargetType="{x:Type cc:BackgroundControl}"
>
    <
Setter Property="Grid.ColumnSpan" Value="4"
/>
    <
Setter Property="Grid.RowSpan" Value="3"
/>
    <
Setter Property="Template"
>
      <
Setter.Value
>
        <
ControlTemplate TargetType="{x:Type cc:BackgroundControl}"
>
          <
Grid
>
            <
Rectangle
>
              <
Rectangle.Fill
>
                <
LinearGradientBrush StartPoint="0,0" EndPoint="0,1"
>
                  <
LinearGradientBrush.GradientStops
>
                    <
GradientStop Offset="0" Color="White"
/>
                    <
GradientStop Offset="1" Color="#cc99ccff"
/>
                  </
LinearGradientBrush.GradientStops
>
                </
LinearGradientBrush
>
              </
Rectangle.Fill
>
            </
Rectangle
>
          </
Grid
>
        </
ControlTemplate
>
      </
Setter.Value
>
    </
Setter
>
  </
Style
>
  ...
</ResourceDictionary>


The Windows application

To apply themes from the external assembly, I just deploy it into the windows application directory and I choose to load the dictionaries when the application starts.
Because I do not want to reference this assembly, I use an Uri to load dictionaries. Why I don't want to use a reference ? The reason is very simple, I just suppose that during the life of my application, the design team could create another theme that could be applied in deploying this new assembly into the application directory. I just want the themes to be applied dynamically.
The code below doesn't fully implement this concept but the base mechanisms are here:

public partial class MyApp : Application
{
 
private List<ResourceDictionary
> _themeList;

 
public
MyApp()
  {
    _themeList =
new List<ResourceDictionary
>(4);
  }

  internal List<ResourceDictionary> AvailableThemes
  {
   
get { return
_themeList; }
  }

  private void ApplicationStartUp(object sender, StartupEventArgs e)
  {
    _themeList.Add((
ResourceDictionary)Application.LoadComponent(new Uri(@"RD.CustomThemes;;;component\themes/customthemes.luna.baml", UriKind
.Relative)));
    _themeList.Add((
ResourceDictionary)Application.LoadComponent(new Uri(@"RD.CustomThemes;;;component\themes/customthemes.xbox.baml", UriKind
.Relative)));
    _themeList.Add((
ResourceDictionary)Application.LoadComponent(new Uri(@"RD.CustomThemes;;;component\themes/customthemes.toon.baml", UriKind
.Relative)));
  }
}

The Uri must use a particular format: AssemblyName;vVersion;PublicKeyToken;component\ThemeResourceName

The code below enables to switch from one theme to another without forgetting the default one:

protected override void OnKeyDown(KeyEventArgs e)
{
 
base
.OnKeyDown(e);
 
if (e.Key == Key
.F5)
  {
    ++_themeIndex;

   
if (_themeIndex > this
.MyApplication.AvailableThemes.Count - 1)
   {
      _themeIndex = -1;
     
Application.Current.Resources = null
;
    }
   
else
     
Application.Current.Resources = this
.MyApplication.AvailableThemes[_themeIndex];
  }
}


Conclusion

As you was able to see, with WPF, Microsoft introduces a new business for windows applications e.g. in the industrial application design process, the UI can be externalized and can be in charge by a different team from the development. I think that in a near future, we will see companies propose skins and themes for many types of application. Furthermore the rioterdeckers are integrating these concepts in an evolved UI engine. Stay tuned...

Download the sources
Download the binaries

(WinFX February CTP)

posted by Chaz | 10 Comments
Filed Under:

[WPF Core] Attached DependencyProperty and VisualTree

Introduction


After several month of investigation about WPF, We are used to working on enhanced user graphical experience and the foundamentals are quickly forgotten.
So I 've decided to stop one minute on one of the most famous mechanism that allows a parent to provide functionalities to its children, its descendants or much more.

Whereas nobody can ignore the law, nobody can ignore the DependencyProperty. You certainly used a canvas to position your visual elements using absolute position and you might notice that the children of the canvas were able to use canvas capabilities to define this postion. The code below can easily refresh your memory:

<Canvas>
  <
Button Canvas.Top="10" Canvas.Left="30" Height="30" Width="50"
/>
</
Canvas>

The button is decorated by the attached DependencyProperty objects of its parent which is the canvas. So now how to implement this powerful mechanism ?

Creating the attached DependencyProperty


To describe the concept, I suggest to create a panel that allows displaying a disclaimer message, i.e. each child of this panel is decorated with this feature.
So I consume the panel as describes below:

<l:DisclaimerPanel>
  <
Button Height="35" Width="90" Content="Disclaimer child" l:DisclaimerPanel.Disclaimer="Copyright © 2006 The RioterDeckers" Margin="0,40,0,10"
/>
  <
Button Height="35" Width="90" Content="Disclaimer child" l:DisclaimerPanel.Disclaimer="Powered by the RioterDeckers" Margin="0,40,0,10"
/>
</
l:DisclaimerPanel>

Figure 1. the disclaimer panel and its children

So, in the DisclaimerPanel class, I first create the DependencyProperty. Then I register and attach it into the DependencyProperty system:

private static readonly DependencyProperty DisclaimerProperty;

static DisclaimerPanel()
{
 
DisclaimerProperty =
DependencyProperty.RegisterAttached("Disclaimer", typeof(string), typeof(DisclaimerPanel), new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(_DisclaimerChangedCallBack)), new ValidateValueCallback(_ValidateValueCallBack));
}

When you declare an attached DependencyProperty, WPF needs at least one accessor method to be called when the value of the property is set. I need no more energy to create another method to get the value of my attached property. Be careful that WPF defines a naming constraint for these method and so in my case, must be SetPropertyName and GetPropertyName and respect a particular signature.

public static string GetDisclaimer(UIElement element)
{
 
return (string
)element.GetValue(DisclaimerProperty);
}
public static void SetDisclaimer(UIElement element, string value)
{
  element.SetValue(DisclaimerProperty, value);
}

Defining a behavior when the DependencyProperty is set


In the RegisterAttached method signature, the PropertyChangedCallBack parameter enables to define a behavior to execute when a value is set to the DependencyProperty. In my case, I would like to create the visual tree for the specified disclaimer message.

private static void _DisclaimerChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
 
if(d == null) return
;
 
 
FrameworkElement __element = (d as FrameworkElement
);
  if (__element == null) return
;

  Control __disclaimerControl = _BuildDisclaimerVisualTree(e.NewValue.ToString());
  (__element.Parent
as DisclaimerPanel
).Children.Add(__disclaimerControl);
}

I also use the ValidateValueCallback argument in order to check the good type of the attended value. In order to avoid having a null value, it's recommended to give a default value passed as the first parameter of the RegisterAttached method.

private static bool _ValidateValueCallBack(object value)
{
 
return (value is string
);
}

Building the VisualTree of the disclaimer message


To display the disclaimer message, I don't need to create a complex logical tree, I think that a simple control will be sufficient. I just need to create a visual tree through a ControlTemplate applied to the control and to build the tree I simply use several FrameworkElementFactory objects which are very useful to create the visual composition.

private static Control _BuildDisclaimerVisualTree(string value)
{
 
Control __control = new Control
();

  FrameworkElementFactory __fePanelFactory = new FrameworkElementFactory(typeof(StackPanel));

  FrameworkElementFactory __feFactory = new FrameworkElementFactory(typeof(TextBlock));
  __feFactory =
new FrameworkElementFactory(typeof(TextBlock
));
  __feFactory.SetValue(
TextBlock
.TextProperty, value);
  __feFactory.SetValue(
TextBlock.HorizontalAlignmentProperty, HorizontalAlignment
.Center);
  __feFactory.SetValue(
TextBlock.ForegroundProperty, new SolidColorBrush(Colors
.Red));
  __feFactory.SetValue(
TextBlock
.FontSizeProperty, 9.0d);
  __feFactory.SetValue(
TextBlock.FontStyleProperty, FontStyles
.Italic);

  __fePanelFactory.AppendChild(__feFactory);

  FrameworkElementFactory __feHLTextBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
 
FrameworkElementFactory __feHlinkFactory = new FrameworkElementFactory(typeof(Hyperlink
));
 
FrameworkElementFactory __feSubTBFactory = new FrameworkElementFactory(typeof(TextBlock
));
  __feHLTextBlockFactory.SetValue(
TextBlock.HorizontalAlignmentProperty, HorizontalAlignment
.Center);
  __feHlinkFactory.SetValue(
Hyperlink.NavigateUriProperty, new Uri
(RD_URI));
  __feHlinkFactory.AddHandler(
Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler
(_NavigateToRD));
  __feSubTBFactory.SetValue(
TextBlock
.TextProperty, RD_URI);
  __feSubTBFactory.SetValue(
TextBlock
.FontSizeProperty, 9.0d);

  __feHlinkFactory.AppendChild(__feSubTBFactory);
  __feHLTextBlockFactory.AppendChild(__feHlinkFactory);
  __fePanelFactory.AppendChild(__feHLTextBlockFactory);

  ControlTemplate __template = new ControlTemplate(typeof(Control));
  __template.VisualTree = __fePanelFactory;
  __control.Template = __template;

  return __control;
}

Conclusion


As you can see, WPF provides powerful mechanisms to add extended functionalities during the life cycle of dependent objects that it's the behavior of children like the visual tree on the fly. The sources and the binaries come from the WinFX February CTP.

Download the sources
Download the binaries

posted by Chaz | 3 Comments
Filed Under:

WinFX evolved buttons

If you are like Raskal, you may think it is enough to add classic buttons in the applications whereas the power of WPF can be used to improve UI experiences. You are absolutely right!
In this post (more simple as the previous), I give you a bit of code to template your buttons as you could find in Windows Vista soon.

Figure 1: styles of the templated buttons

In the sample, I prepared two different styles for the button. One for an accept button and another for a cancel button. You will be able to improve the code as you need to create your own style.
To do that, the template of the button is defined using 7 rectangles, triggers to change the style of the button when the mouse is over and out of the button. I also used 2 storyboards to play with the opacity of the button. Of course I applied styles on the rectangles to define the attended colors. So you can change values of the gradients to customize your own button.

Enjoy.

Download the binaries
Download the sources

 

posted by Chaz | 0 Comments
Filed Under:
More Posts Next page »