I prefer to use a DockedExpander class I wrote a while back (the code is included below). This class automatically sets itself up for whatever side of a DockPanel it is docked on.
For example, in:
<DockPanel>
<edf:DockedExpander DockPanel.Dock="Left">
<ListBox ...
</edf:DockedExpander>
<Grid ...
</DockPanel>
The expander will open from the left, with the button facing the right way. But changing it to:
<edf:DockedExpander DockPanel.Dock="Right">
will automatically adjust the rest of the expander to match. Same with "Top" and "Bottom" docking.
I implemented DockedExpander because the thought of copying several hundred lines of WPF's internal code into my project was abhorrent to me. Also, my DockedExpander control automatically adapts to new theme styles because it reads WPF's internal styles.
Here is the code for the DockedExpander class:
public class DockedExpander : Expander
{
static DockedExpander()
{
_directions = new Dictionary<Dock, DirectionData>();
_directions[Dock.Left] = new DirectionData { Reverse = Dock.Right, ExpandDirection = ExpandDirection.Right };
_directions[Dock.Right] = new DirectionData { Reverse = Dock.Left, ExpandDirection = ExpandDirection.Left };
_directions[Dock.Top] = new DirectionData { Reverse = Dock.Bottom, ExpandDirection = ExpandDirection.Down };
_directions[Dock.Bottom] = new DirectionData { Reverse = Dock.Top, ExpandDirection = ExpandDirection.Up };
DockPanel.DockProperty.OverrideMetadata(typeof(DockedExpander), new FrameworkPropertyMetadata
{
PropertyChangedCallback = (obj, e) => ((DockedExpander)obj).UpdateExpandDirection()
});
ExpandDirectionProperty.OverrideMetadata(typeof(DockedExpander), new FrameworkPropertyMetadata
{
PropertyChangedCallback = (obj, e) => { throw new ArgumentException("Cannot set ExpandDirection because DockedExpander always computes its ExpandDirection from the DockPanel.Dock property"); }
});
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
UpdateExpandDirection();
}
private void UpdateExpandDirection()
{
// Can't use GetTemplateChild because non-PART_ names are not guaranteed to stay the same
var dockPanel = FindTwoElementDockPanelUnder(this);
var headerSite = dockPanel.Children[0];
var expandSite = dockPanel.Children[1];
// Compute the docking
Dock myDock = DockPanel.GetDock(this);
DirectionData myDockData = _directions[myDock];
DockPanel.SetDock(headerSite, myDockData.Reverse);
DockPanel.SetDock(expandSite, myDock);
headerSite.SetValue(FrameworkElement.StyleProperty, myDockData.HeaderSiteStyle);
}
private static Dictionary<Dock, DirectionData> _directions;
private class DirectionData
{
public Dock Reverse;
public ExpandDirection ExpandDirection;
public Style HeaderSiteStyle
{
get
{
if(_headerSiteStyle==null)
{
var expander = new Expander { ExpandDirection = this.ExpandDirection };
expander.BeginInit();
expander.EndInit();
expander.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var headerSite = FindTwoElementDockPanelUnder(expander).Children[0];
_headerSiteStyle = ((FrameworkElement)headerSite).Style;
}
return _headerSiteStyle;
}
}
private Style _headerSiteStyle;
}
private static DockPanel FindTwoElementDockPanelUnder(DependencyObject visual)
{
while(true)
switch(VisualTreeHelper.GetChildrenCount(visual))
{
case 1: visual = VisualTreeHelper.GetChild(visual, 0); continue;
case 2: return visual as DockPanel;
default: return null;
}
}
}
As usual, you need a namespace declaration (xmlns) in your XAML to be able to use a custom control.