Monday, April 27, 2009

I’ve seen quite a few slideshow controls for Silverlight. Mostly they are either retemplated listboxes (buying an easy transition effect for free) or custom controls. I’d like to see how the DomainUpDown control can be used to do a slide show. This post also takes a more indepth look at TransitioningContentControl and along the way shows how to retemplate and inherit/extend other controls.

The DomainUpDown control allows you to visualize a ‘domain’, which can be anything. In our case, our domain will consist of UIElements. The TransitionContentControl is an experimental control that I haven’t featured on this blog yet. It is a ‘convenience’ control, with which I mean to say that the control just takes care of a very common goal in Silverlight development. In this case, it will react to changes in Content by transitioning old content out and transitioning new content in. I’ve talked briefly about the control here.

By using DomainUpDown (henceforth: DUD), we will get a nice navigation system for free (up/down arrows) and the option to show our domain in a cyclic way (in other words, after you reach the end, it will jump to the first item again). Also, we have the option to allow shortcuts into the domain, by having the user type in the control. We will disable that though.

I certainly do not like the default look of the control though, with its big up/down buttons. So, we have some work ahead of us.

I’ve started out with a new SL2 application, adding references to:

  • System.Windows.Controls.Toolkit
  • System.Windows.Controls.Toolkit.Input
  • System.Windows.Controls.Toolkit.Layout

These are part of the Silverlight Toolkit.

I’ve also added a folder called Images and put some images in there. Just making it easy on myself, I’ve hardcoded those images into the Items collection of my DUD. I do not want users to be able to type in the DUD, so I set ‘IsEditable’ to false. I know the images are 300*400, so I even hardcoded the width/height of the control. I end up with this Xaml:

<UserControl
  x:Class="BlogImageSlideshow.Page"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:inputToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit">
  <Grid
    x:Name="LayoutRoot"
    Background="White">
    <inputToolkit:DomainUpDown
      Height="300"
      Width="400"
      IsEditable="False">
      <Image
        Source="Images/3410783929_051d93bc86.jpg" />
      <Image
        Source="Images/3412190495_4099006101.jpg" />
      <Image
        Source="Images/3413898641_f975065242.jpg" />
      <Image
        Source="Images/3414953148_cec704b7b8.jpg" />
      <Image
        Source="Images/3415344995_730b5cc814.jpg" />
    </inputToolkit:DomainUpDown>
  </Grid>
</UserControl>

 

When we view the control though, we are in for a surprise:

image

The buttons are way to big, and our image is way too small. This is caused by the way those buttons are created (with paths, set to scale uniformly). In most situations that will be exactly what you would like, however, in our case it most definitely is not. We’ll want to retemplate our control and are going to do that the ‘easy’ way: through Blend.

Once in Blend, I choose to edit the template of the DUD, which creates a style for me. The Object tab reveals the following parts of DUD:

image

To fix the ugly buttons, let’s repeat the process and retemplate the Spinner (which is a ButtonSpinner). That ButtonSpinner is responsible for hosting our content (in the image that is a Grid with two children: ‘Visualization’ and ‘Text’. It is also responsible for displaying the two buttons.

Editing the Spinner reveals these objects:

image

The unnamed [Button] is used to ‘catch’ the mouseclicks that are done on a disabled button. We don’t need it, so I’ll remove it. Also, I don’t want the buttons on the right side, but I want the previous button on the left and the next button on the right. I actually have a great idea: I’ll overlay the buttons and make them transparent. That way, pressing on the right side of the image will move the slideshow forwards and pressing on the left side will make it go backwards.

I also retemplated the buttons: removing the usual gradient, but having some overlay on ‘mouseOver’ to indicate that we can press. Since I’m no designer, I just used a one white rectangle with an opacity of 0 and a linear gradient opacitymask. On MouseOver, I will animate the opacity to 30%. The effect is a gradual whitening at the border of the image.

This is the style for one of the buttons:

<Style x:Key="RepeatButtonStyle1" TargetType="RepeatButton">
    <Setter Property="Background" Value="#FF1F3B53"/>
    <Setter Property="Foreground" Value="#FF000000"/>
    <Setter Property="Padding" Value="3"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="BorderBrush">
        <Setter.Value>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFA3AEB9" Offset="0"/>
                <GradientStop Color="#FF8399A9" Offset="0.375"/>
                <GradientStop Color="#FF718597" Offset="0.375"/>
                <GradientStop Color="#FF617584" Offset="1"/>
            </LinearGradientBrush>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="RepeatButton">
                <Grid x:Name="Root">
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Normal"/>
                            <vsm:VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.Opacity)">
                                        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0.3"/>
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Pressed">
                                <Storyboard/>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Disabled">
                                <Storyboard/>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="FocusStates">
                            <vsm:VisualState x:Name="Focused">
                                <Storyboard/>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Unfocused"/>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <Rectangle Height="Auto" Margin="0,0,0,0" VerticalAlignment="Stretch" Stroke="#FF000000" Opacity="0" x:Name="rectangle" Fill="#FFFFFFFF">
                        <Rectangle.OpacityMask>
                            <LinearGradientBrush EndPoint="-0.015,0.493" StartPoint="0.99,0.497">
                                <GradientStop Color="#00000000"/>
                                <GradientStop Color="#FFFFFFFF" Offset="1"/>
                            </LinearGradientBrush>
                        </Rectangle.OpacityMask>
                    </Rectangle>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
And now for the fun stuff: Animating it!

Currently the status of the project is that I am able to quickly show a bunch of images and I can mouse over the image and click to show the next/previous image. We’re now going to dive into the TransitioningContentControl and use it to create a nice sliding effect.

First some background: in Silverlight and in WPF, you use a ContentControl to visualize any type of content you might have. When setting a new content, the ContentControl will just replace the current visual with a new visual (representing your new content). It uses a ContentTemplate to determine how to visualize your content.
The only feature that TransitioningContentControl adds to this game is an easy way to start an animation to visualize the actual replacing of content. Although there are some technical difficulties (more on that later), it is meant as a drop-in replacement for ContentControl.

The DomainUpDown also uses a ContentControl to show your content. The templatepart is called ‘Visualization’ and we are going to replace that with a TransitioningContentControl. All I did was replace the ContentControl tag with TransitioningContentControl in the template for DomainUpDown.

Now, here comes the catch: because an element can’t be in the visual tree twice (at the same time) in SL2 this creates a bit of a problem when using the TCC (TransitioningContentControl) with FrameworkElements. In SL3 I will solve this quite easily by creating an image out of the content and using that to ‘transition out’ but in SL2 the only easy work-around is to not use elements with this control. That is actually quite easy, because we can use the ContentTemplate to create our elements on the fly. I’ve changed the way I use DUD as follow:

<inputToolkit:DomainUpDown
    Height="300" Width="400"
    IsEditable="False" Style="{StaticResource SlideShowStyle}">
<inputToolkit:DomainUpDown.ItemTemplate>
  <DataTemplate>
    <Image Source="{Binding}" />
  </DataTemplate>
</inputToolkit:DomainUpDown.ItemTemplate>
<System:String>Images/3410783929_051d93bc86.jpg</System:String>
<System:String>Images/3412190495_4099006101.jpg</System:String>
<System:String>Images/3413898641_f975065242.jpg</System:String>
<System:String>Images/3414953148_cec704b7b8.jpg</System:String>
<System:String>Images/3415344995_730b5cc814.jpg</System:String>
</inputToolkit:DomainUpDown>

Because the TCC actually has a nice default transition, we could call it a day: there is a nice fade-in/fade-out animation going on. I like it!
However, let’s see if we can actually create a slideshow.

To start off, I went ahead and edited the template for TransitioningContentControl. In Blend3 there seems to be a bug that not the complete template is copied (making it rather useless). Blend2 works fine though.
The inevitable object screenshot shows
image

two ‘presentationSites’. Which enables you to see through my trickery: the only thing I do when content is being set, is copy the content inside ‘CurrentContentPresentation’ to ‘PreviousContentPresentation’ and start a storyboard. These are the storyboards that come out of the box:

image

Now, I fully expect you to create / add your own transitions here. To do that, you would go into xaml and add an empty storyboard. At that point you can use the designer again to style it. However, we’ll be fine by restyling the UpTransition and DownTransition.

Currently they do a nice fade and rendertransform. That doesn’t work for me though, so let’s start with the UpTransition, I’d like to have the PreviousContentPresentationSite move to the left and have the Current come in from the right.
That’s pretty easy, I just animated two rendertransforms to do that, not forgetting to set a nice keyspline for the smooth effect.

Next assignment: we’ll need to let TCC know which transition to use. It has no knowledge of the concept of up or down or whatever transitions you came up with.
To be able to tell TCC which transition to play, I expose a DependencyProperty of type string. That indicates the exact name of the transition that should be used the next time the content is changed.

There are many ways to do this. You could hook into the buttons and set the Transition manually. I believe the easiest way though, is to inherit from DomainUpDown and add the functionality yourself:

public class SlidingDomainUpDown : DomainUpDown
{
    private TransitioningContentControl _visualizer;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        _visualizer = GetTemplateChild("Visualization") as TransitioningContentControl;
    }

    protected override void OnDecrement()
    {
        if (_visualizer != null)
        {
            _visualizer.Transition = "DownTransition";
        }
        base.OnDecrement();
    }

    protected override void OnIncrement()
    {

        if (_visualizer != null)
        {
            _visualizer.Transition = "UpTransition";
        }

        base.OnIncrement();
    }
}

 

Interestingly enough, I get asked how to get to templateparts all the time, on the silverlight.net forums. Well, here you go: the simplest possible example there is. I hope this is clear!

At this point, everything works fine for me. Except that the effect is horrible! You see the complete image moving, but you would expect to only see it inside of the frame. What we need is some clipping.
I’m no Blend hero, but I pulled it off by creating a rectangle and right-clicking, choosing Path/make clipping path. Then I chose the TCC element, called ‘Visualization’ to be the clipping target. It ended up creating this xaml:

Clip="M0.5,0.5 L395.5,0.5 L395.5,295.5 L0.5,295.5 z"

We’re now at a point that I’m really happy about! The user has some feedback when he mouses over the image and the effect is quite nice. The user can click too fast though (while the transition is in motion) and I’d like to disable that:

protected override void OnValueChanging(RoutedPropertyChangingEventArgs<object> e)
{
    base.OnValueChanging(e);

    if (_visualizer != null && _visualizer.IsTransitioning)
    {
        e.Cancel = true;
    }
}

It would have been nicer to disable the buttons, but this will do for now.

Taking the extra step: adding direct shortcuts

This is becoming a huge post, but I’m having too much fun.
You have seen slideshows that have little indicators on the buttom that allow you to directly navigate to an image. I’m thinking of the Hulu.com example:

image

Let’s create those!

I started by adding a ListBox to the template of our SlidingDomainUpDown control. I cheated a bit and hardlinked the Items collection to the ItemsSource collection of that ListBox.
Then I went on to restyle the ListBoxItems to show a small preview of our item. Then I hooked up the ListBox.SelectionChanged event. When the selecteditem changes, I just set the Value of the DomainUpDown. Obviously, this could have been more elegantly done by creating a binding between the Value property and the SelectedItem property of the ListBox.  (oh well, it’s my blog! :) )
I also cheated because I did not spend the time looking at the key handling. The listbox actually takes care of some of the keyhandling, and I don’t like it.

I end up with this:

image

I set up the transition to be the DefaultTransition (which is a nice fade in/fade out). So, now the user can slide or go directly to an image. Loving it!

Try it out here and grab the solution here.

Monday, April 27, 2009 5:28:32 PM (Romance Standard Time, UTC+01:00)  #    Comments [4]  |  Trackback Related posts:
Accordion Part 5: AccordionButton
Accordion Part 4 templating example
Accordion part 3
Accordion, part 2
Accordion, part 1
DomainUpDown

Tuesday, April 28, 2009 7:22:48 AM (Romance Standard Time, UTC+01:00)
Hi,
at http://www.xamltemplates.net/ you can find style for all WPF and Silverlight controls, check it out.
Tuesday, April 28, 2009 3:29:19 PM (Romance Standard Time, UTC+01:00)
o melhor
Bruno
Tuesday, April 28, 2009 4:50:07 PM (Romance Standard Time, UTC+01:00)
Great article, is it possible to add extra animation effects to the transitions?
Thursday, April 30, 2009 5:52:41 AM (Romance Standard Time, UTC+01:00)
@steve li: certainly. The silverlight toolkit has a sample with 3d effects (require silverlight 3 though), but everything you'd like is possible there.
Ruurd
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):