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 [60]  |  Trackback
 Friday, April 17, 2009

Jafar Husain told me that he was working on a challenge by Eric Lippert. David Anson followed and they posted solutions to the challenge on their blog. I thought it was quite a bit of fun so I quickly wrote something up that I like.

I will copy the problem from Erics website. It challenges you to transform a sequence of strings into a nicely delimited form.

(1) If the sequence is empty then the resulting string is "{}".
(2) If the sequence is a single item "ABC" then the resulting string is "{ABC}".
(3) If the sequence is the two item sequence "ABC", "DEF" then the resulting string is "{ABC and DEF}".
(4) If the sequence has more than two items, say, "ABC", "DEF", "G", "H" then the resulting string is "{ABC, DEF, G and H}". (Note: no Oxford comma!)

He noted he was mostly interested in readable code, so I came up with a nice Linq solution. However, Jafar was unimpressed and noted that performance was still important. I was traversing the sequence too often for his taste. Back to the drawing board and coming up with a less readable, but pretty quick implementation:

string lastWord = String.Empty;

StringBuilder sb = new StringBuilder();
foreach(string s in input)
{
    sb.Append(s);
    sb.Append(", ");
    lastWord = s;
}

int lastWordIndex = (sb.Length -1) - lastWord.Length - 3;

if (lastWordIndex > 0)
{
    Console.WriteLine("{" + sb.ToString(0, lastWordIndex) + " and " + lastWord + "}");
}
else if (sb.Length == 0)
{
    Console.WriteLine("{}");
}
else
{
    Console.WriteLine("{" + sb.ToString(0, sb.Length -2) + "}");
}

 

I like this solution quite a bit. Obviously not optimized the return statements, and the readability is quite a bit worse than my first attempt. But it’s short, only goes over the sequence once and should perform well.

edit: The linq version:

StringBuilder t = new StringBuilder();

            int count = input.Count();

            var middleStrings = input.Where((s, i) => i > 0 && i < count - 1);

            t.Append("{");

            t.Append(input.FirstOrDefault());

            // linq reads better than foreach.
            middleStrings.ToList().ForEach(s =>
                                              {
                                                 t.Append(", ");
                                                 t.Append(s);
                                              });

            if (count > 1)
            {
                t.Append(" and ");
                t.Append(input.LastOrDefault());
            }

            t.Append("}");

            return t.ToString();

Oh well.

Friday, April 17, 2009 5:15:54 AM (Romance Standard Time, UTC+01:00)  #    Comments [33]  |  Trackback
 Sunday, April 12, 2009

In a previous post I mentioned ways to speed up IE8. For me, having used Spybot a long time ago proved to be the culprit: it had added a slew of sites (hundreds) to the blocked sites zone. I removed it and all was well..

Or was it?

I noticed that IE8 still did not come up as quick as it did on my other computers. Opening a tab was fast, but opening another tab proved to be slow. Actually, I found that the first tab I opened was fast, the next one was slow, the following was fast again. Possibly some pre-creation of processes going on?

Today I finally did the smart thing and hooked up filemon to see what was actually going on. It turns out that there were still hundreds of domains somewhere hidden in my registry. They no longer showed up in any dialog box concerning security, but they were there! I removed the key and now IE8 is really fast. As in, faster than Chrome or Firefox, when opening new tabs. Incredible!

So, if you feel that IE8 is still a bit sluggish for you, go and see what the following registry key contains:

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains

(Note that I’m on a x64 machine, hence the WOW64 node).

My solution is to delete the Domains key.

(I can’t even convey how happy I am with this simple find. We all spend a lot of our time inside our browser and I like IE8 better than other browsers. It is just a very capable and well-thought-out user experience. But if something is slow, it kills the experience!)

Sunday, April 12, 2009 1:29:47 AM (Romance Standard Time, UTC+01:00)  #    Comments [26]  |  Trackback
 Saturday, April 11, 2009

I believe this is going to be the last post in this series, although I bet I will have some loose Accordion posts coming up. This part is going to focus on AccordionButton, and show you how to retemplate it.

Previous posts:
Part 1 – accordion
Part 2 – accordion item
Part 3 – expandable content control
Part 4 – retemplating, real world example

We’ve already hit on a lot of the internals of AccordionItem. Expandable content control was used to easily animate the size of the content. When looking at the header though, it gets complicated all over again.

This is the important part of AccordionItem, defining both a place for the content and a place for the header:

<Border x:Name="Background" 
        Padding="{TemplateBinding Padding}" 
        BorderBrush="{TemplateBinding BorderBrush}" 
        BorderThickness="{TemplateBinding BorderThickness}" 
        CornerRadius="1,1,1,1">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" x:Name="rd0"/>
            <RowDefinition Height="Auto" x:Name="rd1"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" x:Name="cd0"/>
            <ColumnDefinition Width="Auto" x:Name="cd1"/>
        </Grid.ColumnDefinitions>

        <layoutPrimitivesToolkit:AccordionButton
                    x:Name="ExpanderButton"
          Style="{TemplateBinding AccordionButtonStyle}"
                    Content="{TemplateBinding Header}"
                    ContentTemplate="{TemplateBinding HeaderTemplate}"
                    IsChecked="{TemplateBinding IsSelected}"
                    IsTabStop="True"
                    Grid.Row="0"
                    Padding="0,0,0,0"
                    Margin="0,0,0,0"
                    FontFamily="{TemplateBinding FontFamily}"
                    FontSize="{TemplateBinding FontSize}"
                    FontStretch="{TemplateBinding FontStretch}"
                    FontStyle="{TemplateBinding FontStyle}"
                    FontWeight="{TemplateBinding FontWeight}"
                    Foreground="{TemplateBinding Foreground}"
                    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" 
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch" 
          HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
          Background="{TemplateBinding Background}" />

        <layoutPrimitivesToolkit:ExpandableContentControl
                    x:Name="ExpandSite"
                    Grid.Row="1"
                    IsTabStop="False"
                    Percentage="0"
                    RevealMode="{TemplateBinding ExpandDirection}"
                    Content="{TemplateBinding Content}"
                    ContentTemplate="{TemplateBinding ContentTemplate}"
                    Margin="0,0,0,0"
                    FontFamily="{TemplateBinding FontFamily}"
                    FontSize="{TemplateBinding FontSize}"
                    FontStretch="{TemplateBinding FontStretch}"
                    FontStyle="{TemplateBinding FontStyle}"
                    FontWeight="{TemplateBinding FontWeight}"
                    Foreground="{TemplateBinding Foreground}"
                    HorizontalContentAlignment="Left"
                    VerticalContentAlignment="Top" 
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"/>
    </Grid>
</Border>


As you can see, both ExpandableContentControl and AccordionButton are placed in the primitives namespace. More importantly, they are placed in a grid. As I’ve shown in previous posts, that is how we react to the ExpandDirection setting on accordion (so placing the content in the first column and the button in the second would correspond to the ExpandDirection ‘right’).

AccordionButton is an extremely simple class. It inherits from ToggleButton, and its whole purpose in life is to be able to react (once again) to different ExpandDirections. So, if you were to open up the code of AccordionButton, you would see that all it does is this:

switch (ParentAccordionItem.ExpandDirection)
{
    case ExpandDirection.Down:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandDown);
        break;

    case ExpandDirection.Up:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandUp);
        break;

    case ExpandDirection.Left:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandLeft);
        break;

    default:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandRight);
        break;
}

Ofcourse, the template of AccordionButton is quite big. This is the reason I introduced this class, just to simplify the template (considerably).

It is time to look at the important part of AccordionButtons template:

<Border x:Name="background" Background="{TemplateBinding Background}" CornerRadius="1,1,1,1">
    <Grid>
        <Border Height="Auto" Margin="0,0,0,0" x:Name="ExpandedBackground" VerticalAlignment="Stretch" Opacity="0" Background="#FFBADDE9" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1,1,1,1"/>
        <Border Height="Auto" Margin="0,0,0,0" x:Name="MouseOverBackground" VerticalAlignment="Stretch" Opacity="0" Background="#FFBDBDBD" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1,1,1,1"/>
        <Grid Background="Transparent">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" x:Name="cd0"/>
                <ColumnDefinition Width="Auto" x:Name="cd1"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" x:Name="rd0"/>
                <RowDefinition Height="Auto" x:Name="rd1"/>
            </Grid.RowDefinitions>
            <Grid Height="19" HorizontalAlignment="Center" x:Name="icon" VerticalAlignment="Center" Width="19" RenderTransformOrigin="0.5,0.5" Grid.Column="0" Grid.Row="0">
                <Grid.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform Angle="-90"/>
                        <TranslateTransform/>
                    </TransformGroup>
                </Grid.RenderTransform>
                <Path 
                    Height="Auto" 
                    HorizontalAlignment="Center" 
                    Margin="0,0,0,0" x:Name="arrow" 
                    VerticalAlignment="Center" 
                    Width="Auto" 
                    RenderTransformOrigin="0.5,0.5" 
                    Stroke="#666" 
                    StrokeThickness="2" 
                    Data="M 1,1.5 L 4.5,5 L 8,1.5">
                    <Path.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform/>
                            <SkewTransform/>
                            <RotateTransform/>
                            <TranslateTransform/>
                        </TransformGroup>
                    </Path.RenderTransform>
                </Path>
            </Grid>
            <layoutToolkit:LayoutTransformer
                FontFamily="{TemplateBinding FontFamily}" 
                FontSize="{TemplateBinding FontSize}" 
                FontStretch="{TemplateBinding FontStretch}" 
                FontStyle="{TemplateBinding FontStyle}" 
                FontWeight="{TemplateBinding FontWeight}" 
                Foreground="{TemplateBinding Foreground}" 
                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                Margin="6,6,6,0" 
                x:Name="header" 
                Grid.Column="0"
                Grid.Row="0" 
                Grid.RowSpan="1" 
                Content="{TemplateBinding Content}" 
                ContentTemplate="{TemplateBinding ContentTemplate}"/>
        </Grid>
    </Grid>
</Border>

There are two parts here. We have a grid (named ’icon’) and a LayoutTransformer. Again, we see the old trick of a grid with 2 columns and 2 rows: it is used to place the icon relative to the header.

Layouttransformer: it is a class that my teammate Delay has developed and was able to ship with the toolkit. He has a whole host of blog posts about it, and his latest may be of most interest to you: it shows how to animate the layoutTransformer.
LayoutTransformer is a control that allows me to rotate the header and keeping the layout engine happy. In other words, I rotate the header 90 degrees, and the width and height of the header is correct. That would not be the case if I had used RenderTransform.
I will not focus on this class any more, please checkout Delays posts on it!

By now, you should have enough understanding of what is going on to be able to retemplate AccordionButton:

  1. It holds both the Header and an icon
  2. It reacts to expanddirection
  3. It re-arranges the location of the button and header accordingly

Actually, it also rotates the icon. So when you are in ExpandDirection ‘right’, the arrow in the button will still point towards the content, even though its location is completely different from ExpandDirection ‘left’.

Templating the AccordionButton

We have hidden AccordionButton from the designer surface. We did that because we feel that, as a primitive, there is no value in having AccordionButton clutter up the design toolbox. However, in order to template it, it is easiest to use blend with only an AccordionButton there.. So I used this Xaml in Blend:

<UserControl x:Class="AccordionBlogSamplesSL2.HorizontalImages"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:layoutToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit" 
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" xmlns:System_Windows_Controls_Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Layout.Toolkit" 
    >
  <Grid x:Name="LayoutRoot" Background="White">
      <System_Windows_Controls_Primitives:AccordionButton />
  </Grid>
</UserControl>

That gives me a nice design surface with an AccordionButton on it. When I attempt to ‘Edit template’, Blend will create the style and shows the objects inside of AccordionButton:

image

Not only that, it also shows me these states:

image

When pressing any of the ExpandDirectionStates, you will notice the icon being rotated and moved to different gridcells. It is easiest to remain in the Base state to do your work.

I’m not a terribly good designer (one would argue I am in fact, a terribly bad designer), so I would even attempt to make this look good. I will just change the template slightly so I have something to show you!

The design surface looks like this:

image

I will change the path (called arrow) to this:

image

I’ve also changed the MouseOverBackground to something random and the expanded background.
Quite a few people have asked me how to change those properties, well, here they are. They are part of the AccordionButton template.

Bringing it all together

AccordionItem luckily exposes an AccordionButtonStyle, so in order to restyle our AccordionItems to accept this AccordionButtonStyle, we simply apply it.

<layoutToolkit:Accordion>
    <layoutToolkit:AccordionItem Content="AccordionItem" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"/>
    <layoutToolkit:AccordionItem Content="AccordionItem" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"/>
</layoutToolkit:Accordion>

As I’ve been getting some questions on my blog about styles and templates, I will also show you how to do this with an AccordionItemContainerStyle:

<layoutToolkit:Accordion>
<layoutToolkit:Accordion.ItemContainerStyle>
    <Style TargetType="layoutToolkit:AccordionItem">
     <Setter Property="AccordionButtonStyle" Value="{StaticResource AccordionButtonStyle1}" />
    </Style>
</layoutToolkit:Accordion.ItemContainerStyle>
    <layoutToolkit:AccordionItem Content="AccordionItem" />
    <layoutToolkit:AccordionItem Content="AccordionItem" />
</layoutToolkit:Accordion>

(The latter approach is best, imho)

We end up with:

image

I hope that helps!

Saturday, April 11, 2009 9:42:21 PM (Romance Standard Time, UTC+01:00)  #    Comments [155]  |  Trackback