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 7:03:22 PM (Romance Standard Time, UTC+01:00)
Good Job. I really like it :)
Monday, April 06, 2009 7:32:39 PM (Romance Standard Time, UTC+01:00)
How do you use AccordionButtonStyle?
shaggygi
Wednesday, April 08, 2009 8:02:57 PM (Romance Standard Time, UTC+01:00)
Ben helemaal trots op je! Good job.
Thursday, April 09, 2009 5:14:36 PM (Romance Standard Time, UTC+01:00)
@shaggyhi: I promise I'll drive into that subject in a future post

@tumaini: haha, bedankt. jah, dit was leuk ja. Blij te zien dat je mijn blog leest! :) Kom je nog eens naar hier?
Ruurd
Friday, April 10, 2009 10:51:49 AM (Romance Standard Time, UTC+01:00)
Ik wil zekers komen ja!!! Mail mij effe de gegevens wara ik je het beste op kan bereiken!
Friday, April 10, 2009 11:18:59 AM (Romance Standard Time, UTC+01:00)
Ruurd, would love to continue communicating with you in Dutch, but that would be irritating to the people who read this blog and I have some question other might also have...

Although I can do a (little) bit of coding, my orientation towards implementing controls is always with serious design (presentation) changes. Added to that I do think each control should 'fit' in a site design concept. I have been working with the Accordion control and have been especially interested in changing it's look&Feel and insert (via RSS feed) some news items into it.

First problem was that I wanted more control over the look&Feel, as mentioned above. I tried to use some of the examples you gave in your blogs, but they reverted into the 'standard' Look&Feel of the Accordion, in my opinion not granting me enough possibilities to adjust the Look&Feel. I finally found an alternative, where I do have (a lot) more control over the items presented:

var rssFeedItems = new List<SVRssItems>();
rssFeedItems.Add(new SVRssItems("Test Header 1", "Test Content RSS Feed 1"));
rssFeedItems.Add(new SVRssItems("Test Header 2", "Test Content RSS Feed 2"));
rssFeedItems.Add(new SVRssItems("Test Header 3", "Test Content RSS Feed 3"));

//Colors
var transparentBrush = new SolidColorBrush(Colors.Transparent);
var whiteBrush = new SolidColorBrush(Colors.White);
var font = new FontFamily("Verdana");

foreach (SVRssItems item in rssFeedItems)
{
acc.Items.Add(new AccordionItem
{
Header = item.headerRss,
Content = item.contentRss,
Background = transparentBrush,
Foreground = whiteBrush,
BorderBrush = transparentBrush,
FontFamily = font,
FontSize = 12,
Cursor = Cursors.Hand
});
}

Like I mentioned before, I am not a that good developer, so bare with me on that... ;-)

In the foreach loop I do have the possibility to have more control over the presentation of the items, without having to change the styles or templates.
I would like your comments on the following:

1. Is there a better way to work with the flexibility of the Accordion content presentation similar to the sample code above?

2. Do you have a suggestion how to keep content Text within a specified limit (Width and MaxWidth don't seem to work and 'only' add scrollbars if the Text is longer than the initialized set width of the Accordion)

3. Maybe a bit trivial, but how do I change the standard color of the mouse over in code?

Thanx!
Tuesday, January 19, 2010 10:09:12 AM (Romance Standard Time, UTC+01:00)
how can i align the headers of the accordion horizontally and have the expanders open in upwards direction and make it like a menu bar.
Jaskirat
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):