DCSIMG
Build a Better Accordion (Someone? Please?) - Technicals and Technicalities

Technicals and Technicalities

Ariel's uneditable Bliki

Build a Better Accordion (Someone? Please?)

I’ve realized I needed an Accordion control for my WPF application. Implementing one seemed like a hassle, so I’ve looked for one. Amazingly, I have found that Silverlight has one, but WPF does not. Moreover – all the major control vendors don’t have such a control. Strange.

Btw, if you do come across one – do let me know.

 

Then I’ve stumbled upon this blog post about a lightweight implementation for an accordion, based on a StackPanel with Expanders as children.

The accordion had one flaw – it didn’t resize the children when it was resized, only when one of them expanded.

Ariel’s Ugly hack: resize them on the MeasureOverride() method. Problem solved.

I hate myself.

 

So, if you also need an Accordion control and want to hate yourself (or me!) – here’s the code.

 

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
 
namespace TestGen2.UI.UIInfra
{
    public class Accordion: StackPanel
    {
        private const string ExpandSiteName = "ExpandSite";
        private const string HeaderSiteName = "HeaderSite";
 
        static Accordion()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Accordion), new FrameworkPropertyMetadata(typeof(Accordion)));
        }
 
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            InitializeAccordion();
        }
 
        private void InitializeAccordion()
        {
            foreach (Expander childExpender in Children.OfType<Expander>())
            {
                childExpender.Expanded += ChildExpanded;
            }
        }
 
        private double _PreviousHeight;
        protected override Size MeasureOverride(Size constraint)
        {
            var returnedSize = base.MeasureOverride(constraint);
 
            if (constraint.Height == _PreviousHeight)
            {
                return returnedSize;
            }
            _PreviousHeight = constraint.Height;
 
            var expandedExpander = Children.OfType<Expander>().Where(x => x.IsExpanded).FirstOrDefault();
            if (expandedExpander != null)
            {
                double totalCollapsedExpandersHeight = Children.OfType<Expander>().Where(x => !x.IsExpanded).Sum(x => x.ActualHeight);
                double accordionHeight = constraint.Height;
                var expanderContentPresenter =
                    (ContentPresenter)(expandedExpander.Template.FindName(ExpandSiteName, expandedExpander));
                var header = (FrameworkElement)(expandedExpander.Template.FindName(HeaderSiteName, expandedExpander));
 
                expanderContentPresenter.Height = Math.Max(0, 
                    accordionHeight - totalCollapsedExpandersHeight - 
                    (header.ActualHeight + header.Margin.Top + header.Margin.Bottom +
                    expandedExpander.BorderThickness.Top + expandedExpander.BorderThickness.Bottom));
            }
 
            return returnedSize;
        }
    
        private void ChildExpanded(object sender, RoutedEventArgs e)
        {
            var selectedExpander = e.Source as Expander;
 
            if (selectedExpander == null || !Children.OfType<Expander>().Contains(selectedExpander))
            {
                return;
            }
 
            double totalExpanderHeight = 0;
            foreach (Expander otherExpander in Children.OfType<Expander>())
            {
                if (otherExpander == selectedExpander)
                {
                    continue;
                }
 
                if (otherExpander.IsExpanded)
                {
                    var contentPresenter = otherExpander.Template.FindName(ExpandSiteName, otherExpander) as ContentPresenter;
                    if(contentPresenter != null)
                    {
                        totalExpanderHeight -= contentPresenter.ActualHeight;
                    }
                    otherExpander.IsExpanded = false;
                }
                totalExpanderHeight += otherExpander.ActualHeight;
            }
 
            if (selectedExpander.IsExpanded)
            {
                var contentPresenter = selectedExpander.Template.FindName(ExpandSiteName, selectedExpander) as ContentPresenter;
                if(contentPresenter != null)
                {
                    contentPresenter.Height = ActualHeight - totalExpanderHeight - selectedExpander.ActualHeight;
                }
            }
        }
    }
}

Comments

Dew Drop – October 11, 2009 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop &#8211; October 11, 2009 | Alvin Ashcraft&#039;s Morning Dew

# October 11, 2009 5:26 PM

Parnic said:

I'm glad I stumbled across this post as I had the same problem. The only remaining issue is that I can't seem to find a good way to make one of the expanders be expanded by default. If I do that, then all sorts of ugly things happen where the header takes up too much room, the other expanders fall off the bottom of the window, etc. Have you run across this?

# January 22, 2010 8:41 AM

Ariel said:

Sorry, no. :(

# January 28, 2010 3:30 PM

GST said:

its simple...

put as many radio buttons as the number of expanders...

1)Bind the IsExpanded property to IsChecked Property

2)for the default expander, set the radion button IsChecked as true.

keep the radio buttons hidden...

if u need more help , reply.. and i can post the solution..

# March 24, 2010 9:27 PM

Ariel said:

Hey, that's a really nice idea...

I'd like to see it, if it's not too much hassle, just to have a working backup.

# May 21, 2010 1:26 AM

Skyhawk Sale Cessna 172n Plane Performance, Skyhawk Parts 1987 Strut Assembly 1991 Buick Skylark said:

Pingback from  Skyhawk Sale Cessna 172n Plane Performance, Skyhawk Parts 1987 Strut Assembly 1991 Buick Skylark

# May 21, 2010 10:26 AM

1964 Buick Lesabre Wagon Pontiac Parisienne, Bulb Pontiac Parisienne Headlight Assembly said:

Pingback from  1964 Buick Lesabre Wagon Pontiac Parisienne, Bulb Pontiac Parisienne Headlight Assembly

# May 22, 2010 11:51 PM

1979 Ford F 150 For Sale Honda Ridgeline, Honda Ridgeline Halogen Headlights - 347.mfbattle.com said:

Pingback from  1979 Ford F 150 For Sale Honda Ridgeline, Honda Ridgeline Halogen Headlights - 347.mfbattle.com

# May 25, 2010 4:01 AM

1981 - 2007 @ 250c Discount Cobra Portable Gps, Cheap Brother Mfc 250c - 255.defutbolazo.com said:

Pingback from  1981 - 2007 @ 250c Discount Cobra Portable Gps, Cheap Brother Mfc 250c - 255.defutbolazo.com

# May 31, 2010 10:37 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: