April

11

Accordion Part 5: AccordionButton

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: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!

RSS Feed

No comments yet.

Leave a comment

Anti-spam text: (Required) *
To prove you're a person (not a spam script), type the security text shown in the picture. Click here to regenerate some new text. Click to hear an audio file of the anti-spam word

« A bit more(er) than meets the eye [Easily animate and update LayoutTransformer with AnimationMediator!] | Peeps love Silverlight too! »