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 [3]  |  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 [0]  |  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 [3]  |  Trackback
 Monday, April 06, 2009

Mehdi pointed me to a great accordion on this site. It is a horizontal accordion that only uses images. In this post I’ll walk us through creating something similar.

This is what the accordion looks like:image

There is a snazzy effect where the headline pops in from below when you open the item. Go look at it and play a bit, it’s pretty cool!

I started out by adding references to the highlighted 3 dll’s. All 3 are necessary!

image

Then I created my accordion and gave it some fixed size accordion items. I used this Xaml:

    <layoutToolkit:Accordion ExpandDirection="Right" Margin="100">
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Blue" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Red" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Green" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Yellow" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="PowderBlue" />
      </layoutToolkit:AccordionItem>
    </layoutToolkit:Accordion>

As you can see, I set the ExpandDirection to “Right”, so the accordion opens up in the same way as our sample. I have not set a specific Width on the Accordion, which means that the Accordion will only take what it needs.

image

I get a lot of questions on how the Width of the Accordion determines sizing on AccordionItems. You need to remember that the Items will always divide the space equally they get. In our example, setting a fixed Width of 500 on the Accordion would have yielded this result:

image

Now, our AccordionItems exist always of a header and content. In our sample though, we do not see a header at all. So, we will need to retemplate AccordionItem in order to remove that header.
There is a headerTemplate property on Accordion, but keep in mind that that will allow you to determine the look of what goes into the header! In our case, we will completely remove the header from AccordionItem. So, I went into Blend and selected the first AccordionItem and opened up its template:

image

This created a styleResource that is being applied to the first item.
In the template, I removed every element that I don’t care about. The AccordionButton is the little arrow that moves. Leaving me with this:

image

However, we do actually need a header, since it will be the element a user can click to navigate the accordion. So, let’s look at the Grid that hosts ExpandSite:

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

You can see that the grid has 2 rows and 2 columns. This is all that is needed to position the header and content in all the possible expandDirections. Just for fun, I’ll show you the VisualState “ExpandedRight”:

<vsm:VisualState x:Name="ExpandRight">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.ColumnSpan)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.RowSpan)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.Row)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.Column)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="rd0" Storyboard.TargetProperty="Height">
            <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="cd1" Storyboard.TargetProperty="Width">
            <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</vsm:VisualState>

As you can see, it is happily placing items in the correct location and setting heights and widths on the grid cells. Since we do want the header to have a width, I’ll give the first column (where our header would have been) a minWidth. I will also remove all the Expand visualstates since I don’t need those and they only serve to bloat the Xaml at this point.

Since we removed AccordionButton, we have nothing to press and open the Item. But that’s not what we want anyway, we want to open items as we mouse over them. So I’ve introduced an eventhandler for the MouseEnter event on AccordionItem:

void item_MouseEnter(object sender, MouseEventArgs e)
{
    AccordionItem item = (AccordionItem) sender;
    item.IsSelected = true;
}

Now we’re getting somewhere! The items behave quite similar to our sample. Still need to figure out the header and content parts though. The ExpandSite will always contract to 0 width when it is collapsed (not selected). That can’t be what we want.

We removed the header because we did not want it, and want a part of the content to be visible. There are two animations that govern the expand and collapse movements. They are here:

<vsm:VisualStateGroup x:Name="ExpansionStates">
    <vsm:VisualStateGroup.Transitions>
        <vsm:VisualTransition GeneratedDuration="0"/>
    </vsm:VisualStateGroup.Transitions>
    <vsm:VisualState x:Name="Collapsed">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">
                <SplineDoubleKeyFrame KeySpline="0.2,0,0,1" KeyTime="00:00:00.3" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </vsm:VisualState>
    <vsm:VisualState x:Name="Expanded">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">
                <SplineDoubleKeyFrame KeySpline="0.2,0,0,1" KeyTime="00:00:00.3" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </vsm:VisualState>
</vsm:VisualStateGroup>

Let me explain what is going on there. An AccordionItem can be in either Collapsed or Expanded state. I have no way of determining how big an item should be when it is expanded. Remember, it is the accordion that calculates such things at runtime (given the amount of items, and the width of the accordion itself). When the accordion sets a ‘targetsize’, we also calculate a percentage. When we are collapsed, that percentage is 0, when we are fully expanded, that percentage is 1.

The storyboards that you can see here are being started after the AccordionItem has been collapsed or expanded. In other words, they actually do the revealing or hiding of the content. The only thing they have to animate is the percentage, and this way I allow designers to come in and create different kind of animations (changing the keyspline for instance).

The important value here is 0, in the Collapsed storyboard. Turns out we don’t want it to collapse to 0, but to something like 0.2! If we change that storyboard, the item will still be partially visible, even though the accordion feels it is completely collapsed.

image

That was easy. So, let’s start working on some cool headline action.
I’d like to have a headline come in from below as we expand.

So, let’s create a ContentControl to display our header:

                        <Grid>
                            <System_Windows_Controls_Primitives:ExpandableContentControl 
                                HorizontalAlignment="Stretch" 
                                Margin="0,0,0,0" 
                                x:Name="ExpandSite" 
                                VerticalAlignment="Stretch" 
                                HorizontalContentAlignment="Left" 
                                IsTabStop="False" 
                                VerticalContentAlignment="Top" 
                                Content="{TemplateBinding Content}" 
                                ContentTemplate="{TemplateBinding ContentTemplate}" 
                                Percentage="0" 
                                RevealMode="{TemplateBinding ExpandDirection}"/>
                            <ContentControl x:Name="header" 
                                Content="{TemplateBinding Header}" 
                          VerticalAlignment="Bottom" 
                          Visibility="Collapsed" RenderTransformOrigin="0.5,0.5" >
                                <ContentControl.RenderTransform>
                                    <TransformGroup>
                                        <ScaleTransform/>
                                        <SkewTransform/>
                                        <RotateTransform/>
                                        <TranslateTransform/>
                                    </TransformGroup>
                                </ContentControl.RenderTransform>
                            </ContentControl>
                        </Grid>
                    </Border>
                </Grid>

And have Blend generate some nice animation effects:

<vsm:VisualState x:Name="Expanded">
<Storyboard>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">
        <SplineDoubleKeyFrame KeySpline="0.2,0,0,1" KeyTime="00:00:00.3" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="header" Storyboard.TargetProperty="(UIElement.Visibility)">
        <DiscreteObjectKeyFrame KeyTime="00:00:00.2000000">
            <DiscreteObjectKeyFrame.Value>
                <Visibility>Visible</Visibility>
            </DiscreteObjectKeyFrame.Value>
        </DiscreteObjectKeyFrame>
    </ObjectAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="60"/>
        <SplineDoubleKeyFrame KeyTime="00:00:00.4000000" Value="0" KeySpline="0,0,0.46900001168251,1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

You can see that the header starts out with Visibility=Collapsed, this way it won’t take up any space and mess up the AccordionItem.
When the item expands, that visibility is toggled and a rendertransform is used to bring it into view.

Let’s finish it off with some images, to make our accordion look nice! I grabbed some images and created a somewhat better header:

  <layoutToolkit:AccordionItem Style="{StaticResource AccordionItemStyle1}" MouseEnter="item_MouseEnter">
  <layoutToolkit:AccordionItem.Header>
    <Border Background="#aa000000" Width="400" Height="80">
      <StackPanel Margin="10">
        <TextBlock FontFamily="Verdana" FontSize="20" Foreground="White">Seamonster</TextBlock> 
        <TextBlock FontFamily="Verdana" FontSize="12" Foreground="White">by Proudlove</TextBlock> 
      </StackPanel>
    </Border>
  </layoutToolkit:AccordionItem.Header>
  <Image Source="/Images/3410783929_051d93bc86.jpg"  />
</layoutToolkit:AccordionItem>

I also changed the animations slightly, to be somewhat slower. The result looks like this:

image

image

I hope this little walkthrough helped.

See the live sample here, and download the project here.
Let me know what you think!

Monday, April 06, 2009 4:37:46 AM (Romance Standard Time, UTC+01:00)  #    Comments [7]  |  Trackback