Introduction
I wrote the HeaderedContentControl and Expander for Silverlight Toolkit. They both exist in WPF, and I wrote them to be as much compatible with WPF as possible. But there are significant differences between WPF and Silverlight, so not surprisingly controls on those two platforms are different as well. The differences are mostly caused by the differences of DependencyProperty system between WPF and Silverlight, and new Silverlight innovations like TemplatePart and VisualState. There have been several search referrals to my blog for keywords "Silverlight" "Expander", I thought I should write a post about the design and implementation of HeaderedContentControl and Expander, so here you go.
HeaderedContentControl
HeaderedContentControl inherits from ContentControl, adding two DependencyProperty Header and HeaderTemplate, and two protected virtual function OnHeaderedChanged and OnHeaderTemplateChanged. Below are HeaderedContentControl in Silverlight Toolkit and WPF. The differences are mostly new features added in WPF 3.5 like HeaderStringFormat and OnHeaderStringFormatChanged, and platform differences between WPF and Silverlight, like HeaderTemplateSelector, LogicalChildren, and HasHeader etc.
Figure 1: HeaderedContentControl in Silverlight Toolkit
Figure 2: HeaderedContentControl in WPF
You can check out HeaderedContentControl overview and play with samples from Silverlight Toolkit codeplex page.
Expander
User Interface
Expander is a subclass of HeaderedContentControl, adding the behavior of expanding/collapsing its Content area. The Silverlight Toolkit home page has a screen shot of a customized Expander:
Figure 3: Customized HeaderedContentControl
And samples you can play with:
Figure 4: HeaderedContentControl sample
Besides mouse interface (clicking header/togglebutton to expand/collapse), Expander also has a keyboard interface: depending on the ExpandDirection, left/right/down/up arrow keys may expand or collapse Expander Content area too.
Implementation
Expander adds two DependencyProperty IsExpand and ExpandDirection, two events Expanded and Collapsed, and their related protected virtual methods OnExpanded and OnCollapsed. Below are implementation of Expander in Silverlight Toolkit and WPF:
Figure 5: Expander in Silverlight Toolkit
Figure 6: Expander in WPF
Control Contract & Customization
What's new in Expander in Silverlight Toolkit, relative to its WPF counterpart, is the control contract:
Figure 7: Expander control contract: template parts and visual states
Expander adds two new visual state groups, besides the common and focus groups:
- ExpansionStates: Expanded & Collapsed
As shown below, the default template of Expander has state animation for Expanded and Collapsed states, using ObjectAnimationUsingKeyFrames to change the Visibility property of Content area (represented by ExpandSite element in default template), to show or hide the Content area. You can customize the expand/collapse behavior easily with Blend, like adding a transition animation to change the Opacity of Content area fade it in and out.
Figure 8: Expanded State Animation
- ExpandDirectionStates: ExpandDown, ExpandUp, ExpandLeft & ExpandRight
Expander has four different ExpandDirection values, each has a different layout. To allow users customize Expander template, there must be a way to specify the four layout in template so Expander code knows which one to pick and what components to show/hide according to the ExpandDirection property. Controls like Slider and ScrollBar used the TemplatePart:
This approach doesn't scale well: Expander has four layouts, thus lots of template parts. And each layout must handle Expanded/Collapsed transitions separately. To avoid this problem, I introduced the ExpandDirectionStates visual state group, so users can customize the layout by handling ExpandDirectionStates, which Blend provides good support for. I have to specify one template part: the header ToggleButton, because Silverlight doesn't allow two way binding, so I have to associate Expander.IsExpanded with ToggleButton.IsChecked in Expander code, to provide the expand/collapse user experience. So this way, the control contract is much cleaner and far more flexible, like it is easy to add a RotateTransform to rotate Expander to new layout when ExpandDirection changes. The screen shot below shows how the default template handles all four layout:
- The whole Header is a templated ToggleButton, so clicking anyway on the Header may expand/collapse the Content area.
- It uses a 2x2 Grid to layout Header and Content area.
- It lays out the Expander according to ExpandDirection by having state animation for ExpandDirectionStates:
- It lays out Header and Content areas by animating their Grid.Row and Grid.Column properties.
- It draws the header toggle button correctly by changing its template.
Figure 9: ExpandLeft State Animation
Conclusion
As always, hope this helps, and comments/suggestions/feedbacks etc are welcome. Thanks!