This is not a trivial thing to do.
Basically you're trying to animate a button between two completely different parts of the visual tree, so you're going to have to template the whole thing and wrap everything up in a parent layout that contains both your expander and the button you're trying to animate.
Templating your expander shouldn't be too difficult, just move your cursor to it's XAML, go to the properties tab, select "Template" and click the little square to the right to generate all the code for the new ControlTemplate. I've done it for you in the code below, but the theming will probably be all wrong so you'll want to do it yourself.
Next step is to create two rectangles, one in the expanders header and one in its content area. Set the size of these to whatever you want your button to be but set the fill to Transparent; they're only placeholders to help with layout, and you'll use them just to align your button control later on.
Now you'll need to go back to your ControlTemplate and wrap all the content (i.e. the top-level border) in a grid. You'll also want to place your actual button as a child of this grid.
Finally, you'll need a behaviour that binds to the two rectangles you created (so it can calculate From and To points), the parent grid (so that it has a parent control it can animate your button relative to) and the expander (so it can trigger animations in response to the expander expanding and collapsing). The actual animation itself can then be a ThicknessAnimation which animates the button's Margin.
Put all that together and you get this:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:WpfApp1.Behaviors"
<UserControl.Resources>
<ControlTemplate x:Key="ExpanderControlTemplate1" TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="True">
<Grid x:Name="Grid">
<DockPanel>
<ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<ToggleButton.FocusVisualStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle Margin="0" SnapsToDevicePixels="True" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.FocusVisualStyle>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
<ContentPresenter x:Name="ExpandSite" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" DockPanel.Dock="Bottom" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
<Button Width="100" Height="50" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top">
<i:Interaction.Behaviors>
<behaviors:AnimatedMarginBehavior ElementA="{Binding ElementName=ElementA}" ElementB="{Binding ElementName=ElementB}" Grid="{Binding ElementName=Grid}" Parent="{Binding RelativeSource={RelativeSource AncestorType=Expander}}" />
</i:Interaction.Behaviors>
</Button>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Right">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="-90"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="180"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ExpandDirection" Value="Left">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Left"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
<Expander Margin="5" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Stretch" IsExpanded="True" Template="{DynamicResource ExpanderControlTemplate1}">
<Expander.Header>
<DockPanel Margin="5">
<TextBlock Margin="5" VerticalAlignment="Center">Hello World</TextBlock>
<Rectangle x:Name="ElementA" Width="100" Height="50" Fill="Transparent" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<StackPanel Orientation="Horizontal">
<Canvas Margin="5" Width="300" Background="Black" />
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type RadioButton}">
<Setter Property="Margin" Value="5" />
</Style>
</StackPanel.Resources>
<RadioButton IsChecked="True">Option 1</RadioButton>
<RadioButton>Option 2</RadioButton>
<RadioButton>Option 3</RadioButton>
<RadioButton>Option 4</RadioButton>
<Rectangle x:Name="ElementB" Width="100" Height="50" Fill="Transparent" />
</StackPanel>
</StackPanel>
</Expander.Content>
</Expander>
</Grid>
And the behavior:
public class AnimatedMarginBehavior : Behavior<FrameworkElement>
{
private ThicknessAnimation Animation = new ThicknessAnimation();
public FrameworkElement ElementA
{
get { return GetValue(ElementAProperty) as FrameworkElement; }
set { SetValue(ElementAProperty, value); }
}
public static readonly DependencyProperty ElementAProperty =
DependencyProperty.Register("ElementA", typeof(FrameworkElement), typeof(AnimatedMarginBehavior),
new PropertyMetadata(default(FrameworkElement), (d, e) => (d as AnimatedMarginBehavior).OnBindingChanged(e)));
public FrameworkElement ElementB
{
get { return GetValue(ElementBProperty) as FrameworkElement; }
set { SetValue(ElementBProperty, value); }
}
public static readonly DependencyProperty ElementBProperty =
DependencyProperty.Register("ElementB", typeof(FrameworkElement), typeof(AnimatedMarginBehavior),
new PropertyMetadata(default(FrameworkElement), (d, e) => (d as AnimatedMarginBehavior).OnBindingChanged(e)));
public Expander Parent
{
get { return GetValue(ParentProperty) as Expander; }
set { SetValue(ParentProperty, value); }
}
public static readonly DependencyProperty ParentProperty =
DependencyProperty.Register("Parent", typeof(Expander), typeof(AnimatedMarginBehavior),
new PropertyMetadata(default(Expander), (d, e) => (d as AnimatedMarginBehavior).OnBindingChanged(e)));
public Grid Grid
{
get { return GetValue(GridProperty) as Grid; }
set { SetValue(GridProperty, value); }
}
public static readonly DependencyProperty GridProperty =
DependencyProperty.Register("Grid", typeof(Grid), typeof(AnimatedMarginBehavior),
new PropertyMetadata(default(Grid), (d, e) => (d as AnimatedMarginBehavior).OnBindingChanged(e)));
private void OnBindingChanged(DependencyPropertyChangedEventArgs e)
{
if (this.ElementA == null)
return;
if (this.ElementB == null)
return;
if (this.Parent == null)
return;
if (this.Grid == null)
return;
// set initial position based on whether or not the control is expanded
var currentElement = this.Parent.IsExpanded ? this.ElementB : this.ElementA;
UIElement container = VisualTreeHelper.GetParent(this.Grid) as UIElement;
var pos = currentElement.TranslatePoint(new Point(0, 0), container);
this.AssociatedObject.SetValue(FrameworkElement.MarginProperty, new Thickness(pos.X-1, pos.Y-1, 0, 0));
// get notification when the expander changes state
this.Parent.Collapsed += (_s1, _e1) =>
{
container = VisualTreeHelper.GetParent(this.Grid) as UIElement;
var from = this.ElementB.TranslatePoint(new Point(0, 0), container);
var to = this.ElementA.TranslatePoint(new Point(0, 0), container);
this.Animation.From = new Thickness(from.X, from.Y, 0, 0);
this.Animation.To = new Thickness(to.X, to.Y, 0, 0);
this.Animation.Duration = TimeSpan.FromMilliseconds(500);
this.Animation.EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut };
this.AssociatedObject.BeginAnimation(FrameworkElement.MarginProperty, this.Animation);
};
this.Parent.Expanded += (_s2, _e2) =>
{
container = VisualTreeHelper.GetParent(this.Grid) as UIElement;
var from = this.ElementA.TranslatePoint(new Point(0, 0), container);
var to = this.ElementB.TranslatePoint(new Point(0, 0), container);
this.Animation.From = new Thickness(from.X, from.Y, 0, 0);
this.Animation.To = new Thickness(to.X, to.Y, 0, 0);
this.Animation.Duration = TimeSpan.FromMilliseconds(500);
this.Animation.EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut };
this.AssociatedObject.BeginAnimation(FrameworkElement.MarginProperty, this.Animation);
};
}
}
That's a very rough-and-ready example that gives a general idea of how to do this, you'll definitely want to go over it in detail and clean it up a bit.
Result:
