Thursday, March 26, 2009

I’m glad to see accordion getting the excitement that it’s getting. I’ve gotten great feedback, keep it coming!

Part 1 was concerned with Accordion itself, we will now focus on the individual parts of Accordion.

AccordionItem

AccordionItem is to Accordion as is ListboxItem to Listbox. Accordion will only work with AccordionItems, and that is why, if you feed Accordion an item that is not of type AccordionItem, it will wrap it inside of one.

AccordionItem has several important jobs:

  • It needs to display a header and content.
  • It needs to be able to ‘open’ and ‘close’
  • It needs to be able to work in several ExpandDirections (Left, Right, Top, Bottom)

AccordionItem mimicks Expander in many ways (although I changed the template considerably) but inherits from HeaderedContentControl. Therefore, it has two important properties it gains from HeaderedContentControl: HeaderTemplate and ContentTemplate.
These templates are used to allow you to customize the header and content.

I will use KeyValue pairs to easily create a few AccordionItems:

            acc.ItemsSource = new[]
                                  {
                                          new KeyValuePair<string, string>("A header", "And the content of the accordion item"),
                                          new KeyValuePair<string, string>("Hello", "World")
                                   };

Let’s take a look at an Accordion and it’s Xaml:

image

ExpandDirection

An AccordionItem has the same ExpandDirection property as Accordion. When contained within an Accordion, AccordionItem will get the correct ExpandDirection from Accordion and will not allow you to change it individually.

ExpandDirection may only be set by Accordion, to prevent from weird mixes of ExpandDirections. Unfortunately, that does limit a few scenario’s (as in horizontal Accordion layout with vertical AccordionItems). If this turns out to be a common featurerequest, I’ll look into opening this up.

Accordion.ItemContainerGenerator

Accordion exposes an ItemContainerGenerator which has very helpful methods, such as ‘ContainerFromItem’ and even ContainerFromIndex’. So it is really easy to go from AccordionItem, to Item and vice versa.
This code might make you happy:

            for (int i = 0; i < acc.Items.Count; i++)
            {
                AccordionItem item = acc.ItemContainerGenerator.ContainerFromIndex(i) as AccordionItem;
             }
Locking mechanism

In certain SelectionModes, Accordion must make sure that at least one item is selected. It does so by locking an item if it is the last one open. If you somehow force it to close (through code), Accordion will just open the first AccordionItem in the list. However, that is not that simple, since an AccordionItem may not be unselected, while it is locked.

Since AccordionItem will actually throw an exception when it is unselected while locked, I expose a boolean ‘IsLocked’ that you can use to make sure you don’t accidently do this.
I expose a VisualState that allows you to visualize the lock if you’d like.

The best way to unselect the AccordionItem while in such a mode, is to select a different item.

ExpandableContentControl and AccordionButton

These are two template parts on the AccordionItem. The first takes care of opening and closing in a nice fashion and the latter makes it easier to template the header. It is not necessary, but adds a nice touch. I will talk more about templating them in a follow up post.
In the meantime, it is noteworthy that there is a property AccordionButtonStyle that you can use to style the button more easily.

Selected and Unselected events

Subscribe to these events to know when the user has selected an AccordionItem. Alternatively, you can use the SelectionChanged event on Accordion.

Layout

Under the covers, there is a lot of layout action going on! The item needs to know how to open itself, and also needs to be told _when_ to do so. The actual opening and closing does not correspond to the IsSelected state. In other words: IsSelected will be set whenever an item is selected, which could mean the AccordionItem is still closed. Accordion will instruct AccordionItem to actually open to visualize the new IsSelected state.

Templating

The most important part of the template is:

  1           <Border x:Name="Background" 
  2 			      Padding="{TemplateBinding Padding}" 
  3 			      BorderBrush="{TemplateBinding BorderBrush}" 
  4 			      BorderThickness="{TemplateBinding BorderThickness}" 
  5 			      CornerRadius="1,1,1,1">
  6               <Grid>
  7                   <Grid.RowDefinitions>
  8                       <RowDefinition Height="Auto" x:Name="rd0"/>
  9                       <RowDefinition Height="Auto" x:Name="rd1"/>
 10                   </Grid.RowDefinitions>
 11                   <Grid.ColumnDefinitions>
 12                       <ColumnDefinition Width="Auto" x:Name="cd0"/>
 13                       <ColumnDefinition Width="Auto" x:Name="cd1"/>
 14                   </Grid.ColumnDefinitions>
 15 
 16                   <layoutPrimitivesToolkit:AccordionButton
 17 					          x:Name="ExpanderButton"
 18                     Style="{TemplateBinding AccordionButtonStyle}"
 19 					          Content="{TemplateBinding Header}"
 20 					          ContentTemplate="{TemplateBinding HeaderTemplate}"
 21 					          IsChecked="{TemplateBinding IsSelected}"
 22 					          IsTabStop="True"
 23 					          Grid.Row="0"
 24 					          Padding="0,0,0,0"
 25 					          Margin="0,0,0,0"
 26 					          FontFamily="{TemplateBinding FontFamily}"
 27 					          FontSize="{TemplateBinding FontSize}"
 28 					          FontStretch="{TemplateBinding FontStretch}"
 29 					          FontStyle="{TemplateBinding FontStyle}"
 30 					          FontWeight="{TemplateBinding FontWeight}"
 31 					          Foreground="{TemplateBinding Foreground}"
 32 					          VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" 
 33 					          HorizontalAlignment="Stretch"
 34 					          VerticalAlignment="Stretch" 
 35                     HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
 36                     Background="{TemplateBinding Background}" />
 37 
 38                   <layoutPrimitivesToolkit:ExpandableContentControl
 39 					          x:Name="ExpandSite"
 40 					          Grid.Row="1"
 41 					          IsTabStop="False"
 42 					          Percentage="0"
 43 					          RevealMode="{TemplateBinding ExpandDirection}"
 44 					          Content="{TemplateBinding Content}"
 45 					          ContentTemplate="{TemplateBinding ContentTemplate}"
 46 					          Margin="0,0,0,0"
 47 					          FontFamily="{TemplateBinding FontFamily}"
 48 					          FontSize="{TemplateBinding FontSize}"
 49 					          FontStretch="{TemplateBinding FontStretch}"
 50 					          FontStyle="{TemplateBinding FontStyle}"
 51 					          FontWeight="{TemplateBinding FontWeight}"
 52 					          Foreground="{TemplateBinding Foreground}"
 53 					          HorizontalContentAlignment="Left"
 54 					          VerticalContentAlignment="Top" 
 55 					          HorizontalAlignment="Stretch"
 56 					          VerticalAlignment="Stretch"/>
 57               </Grid>
58 </Border>

Lines 7 through 14 create a grid with 2 columns and 2 rows.
Line 16 shows the AccordionButton, which is the little arrow + header.The arrow always points to the content and is in different locations, depending on the ExpandDirection.
Line 38 is the ExpandableContentControl, which takes care of the content.

An AccordionItem has, amongst others, 4 visual states for ExpandDirection. I will show the ExpandLeft state:

  1                   <vsm:VisualState x:Name="ExpandLeft">
  2                       <Storyboard>
  3                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpanderButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
  4                               <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
  5                           </ObjectAnimationUsingKeyFrames>
  6                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.ColumnSpan)">
  7                               <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
  8                           </ObjectAnimationUsingKeyFrames>
  9                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpanderButton" Storyboard.TargetProperty="(Grid.RowSpan)">
 10                               <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
 11                           </ObjectAnimationUsingKeyFrames>
 12                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.RowSpan)">
 13                               <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
 14                           </ObjectAnimationUsingKeyFrames>
 15 
 16                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpanderButton" Storyboard.TargetProperty="(Grid.Column)">
 17                               <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
 18                           </ObjectAnimationUsingKeyFrames>
 19                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.Row)">
 20                               <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
 21                           </ObjectAnimationUsingKeyFrames>
 22                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="rd0" Storyboard.TargetProperty="Height">
 23                               <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
 24                           </ObjectAnimationUsingKeyFrames>
 25                           <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="cd0" Storyboard.TargetProperty="Width">
 26                               <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
 27                           </ObjectAnimationUsingKeyFrames>
 28                       </Storyboard>
29 </vsm:VisualState>

Everything in there is actually repositioning the AccordionButton (header) and the ExpandableContentControl (content) inside different grid cells.

In this case, you can imagine the header being in column 1 and the content taking up column 2.

You should not need to have to style an accordionItem itself that much. Most of the template is just about positioning the Header versus the Content. The only reason I see for restyling accordionItem is if you are unhappy with the defaults (easily changed) or with the position of the header and content. All the other styling would be done in either AccordionButton or ExpandableContentControl. Let me know if this is not the case for your scenario.

Thursday, March 26, 2009 8:24:15 AM (Romance Standard Time, UTC+01:00)
This is very nice ,but have ever somebody asked what kind of VS2008 or Blend theme u are using?
I would be lucky to have it.
Please post it,or give me a link.
Zoli
Thursday, March 26, 2009 9:20:55 AM (Romance Standard Time, UTC+01:00)
I have a problem with a TreeView inside an Accordion. Accordion never shows a horizontal scrollbar, so if a TreeView item is too wide, it just gets clipped. Accordion does show a vertical scrollbar, but it is often hidden by the right border of the control (try it and you'll see what I mean).
Mikhail
Thursday, March 26, 2009 5:58:14 PM (Romance Standard Time, UTC+01:00)
Very cool control. I want the AccordionItem's content to occupy the entire space of the AccordionItem. Is it possible?
I created a post on the Silverlight forum: http://silverlight.net/forums/p/84223/196252.aspx#196252
Thanks in advance for your response.
Michel
Thursday, March 26, 2009 10:36:03 PM (Romance Standard Time, UTC+01:00)
Will you be adding the Accordion to the SL Themes in the future? Does that usually get moved over after being added to the SDK? Mature band? Thanks
shaggygi
Saturday, March 28, 2009 6:55:52 AM (Romance Standard Time, UTC+01:00)
@Zoli: I actually switched themes yesterday. The theme you see there is dark night I think. When I find it, I'll post it for you.

@Mikhail: I would recommend that you use Accordion without setting any explicit width's or heights. This includes setting a horizontal or vertical alignment of stretch. If you do set one of these, you will go into 'fixed' mode where the accordion takes up the space you give it (and not more) Currently, it will never show a scrollbar for the complete accordion.
My guess is that in your situation, you gave the accordion a width and have treeview items that are bigger. Just remove the width or horizontal stretch and your golden.

@Michel: This shouldn't really be impossible. I will show you how to retemplate it to achieve the effect. Check back your forum post or here in a few days.

@shaggygi: This is a hard question. I would assume we will take up accordion inside sl themes when it goes to mature, but possibly sooner. I'll check with our themers and get back to you. I don't have a definitive answer for you now though.
Ruurd
Monday, March 30, 2009 10:34:19 AM (Romance Standard Time, UTC+01:00)
Thank you for your help, Ruurd! Unfortunately, I was unable to get an acceptable behavior. Can I send you a small test project by email?
Mikhail
Monday, March 30, 2009 5:31:12 PM (Romance Standard Time, UTC+01:00)
@Mikhail: sure!
Ruurd
Monday, April 06, 2009 2:09:55 AM (Romance Standard Time, UTC+01:00)
@mikhail:

I have received your solution and taken a look at it.
The initial size of the accordion is not big enough to show the scrollbar. I see your point: the initial size is not big enough to show the vertical scrollbar. Setting the first column from 300 width to 350 fixes the issue.

I'm afraid this is the result of conscious decisions we've made. I've seen several people asking for changes in this regard now. We give an item exactly as much as it wants, and do not notify it of size changes (this would be bad for performance during animation). Could you open up a workitem at silverlight/codeplex.com ? I believe the issue warrants a better look at this behavior.
Thanks!
Ruurd
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):