带有支持所有方向的动画的扩展器

Expander with animation that support all direction

我的 Objective: 是创建一个带有展开动画的自定义扩展器,应该支持所有方向。

我尝试了什么: 我在 this 的帮助下实施了一个解决方案。我修改了它并使其根据我的需要工作。我的解决方案将与 Up 一起工作,即使在 Down Direction 下我也可以让它工作。

我现在的问题是什么:我无法让它适用于所有方向。我尝试并未能将 ExpanderDirection 设置为 LeftRight.

这是我的解决方案:

扩展模板:

   <ControlTemplate x:Key="AnimatedExpanderButtonTemp" TargetType="{x:Type ToggleButton}">
        <Border x:Name="ExpanderButtonBorder"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Padding="{TemplateBinding Padding}"
                >
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Rectangle Fill="Transparent"
                           Grid.ColumnSpan="2"/>
                <Ellipse Name="Circle"
                     Grid.Column="0"
                     Stroke="DarkGray"
                     Width="20"
                     Height="20"
                     HorizontalAlignment="Center"
                     VerticalAlignment="Center"
                     />
                <Path x:Name="Arrow"
                  Grid.Column="0"
                  Data="M 1,1.5 L 4.5,5 8,1.5"
                  Stroke="#FF666666"
                  StrokeThickness="2"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"
                  RenderTransformOrigin="0.5,0.5"
                  >
                    <Path.RenderTransform>
                        <RotateTransform Angle="0"/>
                    </Path.RenderTransform>
                </Path>
                <ContentPresenter x:Name="HeaderContent"
                                  Grid.Column="1"
                                  Margin="4,0,0,0"
                                  ContentSource="Content"/>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <!-- Animate arrow when toggled-->
            <Trigger Property="IsChecked"
                     Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="Arrow"
                                             Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)"
                                             To="180"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="Arrow"
                                             Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)"
                                             To="0"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.ExitActions>
            </Trigger>

            <!-- MouseOver, Pressed behaviours-->
            <Trigger Property="IsMouseOver"
                             Value="true">
                <Setter Property="Stroke"
                                Value="#FF3C7FB1"
                                TargetName="Circle"/>
                <Setter Property="Stroke"
                                Value="#222"
                                TargetName="Arrow"/>
            </Trigger>
            <Trigger Property="IsPressed"
                             Value="true">
                <Setter Property="Stroke"
                                Value="#FF526C7B"
                                TargetName="Circle"/>
                <Setter Property="StrokeThickness"
                                Value="1.5"
                                TargetName="Circle"/>
                <Setter Property="Stroke"
                                Value="#FF003366"
                                TargetName="Arrow"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>


    <!-- Slide Out Content Expander's Template, 
    Uses: AnimatedExpanderButtonTemp from above, 
          MultiplyConverter in codebehind-->
    <local:MultiplyConverter x:Key="multiplyConverter" />
    <ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}">
        <DockPanel>
            <Border Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Stretch" DockPanel.Dock="Bottom" Padding="5,3">
                <ToggleButton x:Name="ExpanderButton" HorizontalAlignment="Center" HorizontalContentAlignment="Center"                         
                          Template="{StaticResource AnimatedExpanderButtonTemp}"
                          Content="{TemplateBinding Header}"
                          IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                          OverridesDefaultStyle="True"
                          Padding="1.5,0">
                </ToggleButton>
            </Border>
            <ScrollViewer x:Name="ExpanderContentScrollView" DockPanel.Dock="Top"
                          HorizontalScrollBarVisibility="Hidden"
                          VerticalScrollBarVisibility="Hidden"
                          HorizontalContentAlignment="Stretch"
                          VerticalContentAlignment="Bottom"
                          >
                <ScrollViewer.Tag>
                    <sys:Double>0.0</sys:Double>
                </ScrollViewer.Tag>
                <ScrollViewer.Height>
                    <MultiBinding Converter="{StaticResource multiplyConverter}">
                        <Binding Path="ActualHeight" ElementName="ExpanderContent"/>
                        <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                    </MultiBinding>
                </ScrollViewer.Height>
                <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
            </ScrollViewer>
        </DockPanel>
        <ControlTemplate.Triggers>
            <Trigger Property="IsExpanded" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
                                             Storyboard.TargetProperty="Tag"                                                 
                                             To="1"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
                                             Storyboard.TargetProperty="Tag"                                                 
                                             To="0"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.ExitActions>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

MultiplyConverter.cs(模板需要)

public class MultiplyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double result = 1.0;
        for (int i = 0; i < values.Length; i++)
        {
            if (values[i] is double)
                result *= (double)values[i];
        }

        return result;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new Exception("Not implemented");
    }
}

这样使用:

<Grid >
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Grid Grid.Row="0" Background="Orange">

     <Expander Template="{StaticResource RevealExpanderTemp}" 
              OverridesDefaultStyle="True"
              HorizontalAlignment="Left"
              VerticalAlignment="Top" Background="LightGray">
        <StackPanel>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>                
        </StackPanel>
    </Expander>
    </Grid>
    <Grid Grid.Row="2" Background="Chocolate" />

</Grid>

以上解决方案不适用于 ExpandDirection="Left" or "Right"

关于如何使它适用于所有方向或任何其他替代方案的任何建议?

经过 8 小时的努力,我找到了解决问题的方法。让我回答我自己的问题,这样可以为其他寻求解决方案的人节省一些时间。

扩展器的控制模板:

    <ControlTemplate x:Key="AnimatedExpanderButtonTemp" TargetType="{x:Type ToggleButton}">
        <Border x:Name="ExpanderArrow" CornerRadius="5"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Padding="{TemplateBinding Padding}"
                >
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>                        
                </Grid.ColumnDefinitions>
                <Rectangle Fill="Transparent"
                           Grid.ColumnSpan="2"/>
                <Ellipse Name="Circle"
                     Grid.Column="0"
                     Stroke="DarkGray"
                     Width="20"
                     Height="20"
                     HorizontalAlignment="Center"
                     VerticalAlignment="Center"
                     />
                <Path x:Name="Arrow"
                  Grid.Column="0"
                  Data="M 0 0 L 3,6 L 6,0"
                  Stroke="#FF666666"
                  StrokeThickness="2"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"
                  RenderTransformOrigin="0.5,0.5"
                  >
                    <Path.RenderTransform>
                        <RotateTransform Angle="0"/>
                    </Path.RenderTransform>
                </Path>
                <ContentPresenter x:Name="HeaderContent"                                      
                                  Margin="4,0,0,0"
                                  ContentSource="Content"/>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="DockPanel.Dock"
                     Value="Right">
                <Setter Property="Data" TargetName="Arrow" Value="M 0 0 L 3 3 L 0 6 "/>

            </Trigger>

            <Trigger Property="DockPanel.Dock"
                     Value="Left">
                <Setter Property="Data" TargetName="Arrow" Value="M 0 0 L 3 3 L 0 6"/>

            </Trigger>

            <Trigger Property="DockPanel.Dock"
                     Value="Top">
                <Setter Property="Data" TargetName="Arrow" Value="M 0 0 L 3,6 L 6,0"/>

            </Trigger>



            <!-- Animate arrow when toggled-->
            <Trigger Property="IsChecked"
                     Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="Arrow"
                                             Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)"
                                             To="180"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="Arrow"
                                             Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)"
                                             To="0"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.ExitActions>
            </Trigger>

            <!-- MouseOver, Pressed behaviours-->
            <Trigger Property="IsMouseOver"
                             Value="true">
                <Setter Property="Stroke"
                                Value="#FF3C7FB1"
                                TargetName="Circle"/>
                <Setter Property="Stroke"
                                Value="#222"
                                TargetName="Arrow"/>
            </Trigger>
            <Trigger Property="IsPressed"
                             Value="true">
                <Setter Property="Stroke"
                                Value="#FF526C7B"
                                TargetName="Circle"/>
                <Setter Property="StrokeThickness"
                                Value="1.5"
                                TargetName="Circle"/>
                <Setter Property="Stroke"
                                Value="#FF003366"
                                TargetName="Arrow"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>


    <!-- Slide Out Content Expander's Template, 
    Uses: AnimatedExpanderButtonTemp from above, 
          MultiplyConverter in codebehind-->
    <local:MultiplyConverter x:Key="multiplyConverter" />
    <ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}">
        <DockPanel>
            <!--<Border x:Name="ExpanderButtonBorder" Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Stretch" DockPanel.Dock="Bottom" Padding="5,3">-->
            <ToggleButton x:Name="ExpanderButton" HorizontalAlignment="Stretch" DockPanel.Dock="Bottom"
                          Template="{StaticResource AnimatedExpanderButtonTemp}"
                          Content="{TemplateBinding Header}"
                          IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                          Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}"
                          OverridesDefaultStyle="True"
                          Padding="0">
                </ToggleButton>
            <!--</Border>-->
            <ScrollViewer x:Name="ExpanderContentScrollView" DockPanel.Dock="Top"
                          HorizontalScrollBarVisibility="Hidden"
                          VerticalScrollBarVisibility="Hidden"
                          HorizontalContentAlignment="Stretch"
                          VerticalContentAlignment="Bottom"
                          >
                <ScrollViewer.Tag>
                    <sys:Double>0.0</sys:Double>
                </ScrollViewer.Tag>                    
                <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
            </ScrollViewer>
        </DockPanel>
        <ControlTemplate.Triggers>

            <Trigger Property="ExpandDirection" Value="Right">
                <Setter Property="DockPanel.Dock" TargetName="ExpanderButton" Value="Right"/>
                <Setter Property="DockPanel.Dock" TargetName="ExpanderContentScrollView" Value="Left"/>
                <Setter Property="Width" TargetName="ExpanderContentScrollView">
                    <Setter.Value>
                        <MultiBinding Converter="{StaticResource multiplyConverter}">
                            <Binding Path="ActualWidth" ElementName="ExpanderContent"/>
                            <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                        </MultiBinding>
                    </Setter.Value>
                </Setter>                    
            </Trigger>
            <Trigger Property="ExpandDirection" Value="Up">
                <Setter Property="DockPanel.Dock" TargetName="ExpanderButton" Value="Top"/>
                <Setter Property="DockPanel.Dock" TargetName="ExpanderContentScrollView" Value="Bottom"/>
                <Setter Property="Height" TargetName="ExpanderContentScrollView">
                    <Setter.Value>
                        <MultiBinding Converter="{StaticResource multiplyConverter}">
                            <Binding Path="ActualHeight" ElementName="ExpanderContent"/>
                            <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                        </MultiBinding>
                    </Setter.Value>
                </Setter>
            </Trigger>
            <Trigger Property="ExpandDirection" Value="Left">
                <Setter Property="DockPanel.Dock" TargetName="ExpanderButton" Value="Left"/>
                <Setter Property="DockPanel.Dock" TargetName="ExpanderContentScrollView" Value="Right"/>
                <Setter Property="Width" TargetName="ExpanderContentScrollView">
                    <Setter.Value>
                        <MultiBinding Converter="{StaticResource multiplyConverter}">
                            <Binding Path="ActualWidth" ElementName="ExpanderContent"/>
                            <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                        </MultiBinding>
                    </Setter.Value>
                </Setter>
            </Trigger>

            <Trigger Property="ExpandDirection" Value="Down">                    
                <Setter Property="Height" TargetName="ExpanderContentScrollView">
                    <Setter.Value>
                        <MultiBinding Converter="{StaticResource multiplyConverter}">
                            <Binding Path="ActualHeight" ElementName="ExpanderContent"/>
                            <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                        </MultiBinding>
                    </Setter.Value>
                </Setter>
            </Trigger>
            <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
            </Trigger>


            <Trigger Property="IsExpanded" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
                                             Storyboard.TargetProperty="Tag"                                                 
                                             To="1"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
                                             Storyboard.TargetProperty="Tag"                                                 
                                             To="0"
                                             Duration="0:0:0.4"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.ExitActions>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

MultiplyConverter.cs(模板需要)

public class MultiplyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double result = 1.0;
        for (int i = 0; i < values.Length; i++)
        {
            if (values[i] is double)
                result *= (double)values[i];
        }

        return result;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new Exception("Not implemented");
    }
}

演示:

<!-- Small demo of Expander Template above-->
<Grid >
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Grid Grid.Row="0" Background="Orange">

    <Expander Template="{StaticResource RevealExpanderTemp}"  ExpandDirection="Right"
              OverridesDefaultStyle="True"
              HorizontalAlignment="Left"
              VerticalAlignment="Top" Background="LightGray">
        <StackPanel>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>
            <TextBlock Text="This is Sample Text for Expander" Margin="5"/>                
        </StackPanel>
    </Expander>
    </Grid>
    <Grid Grid.Row="2" Background="Chocolate" />

</Grid>

代码未完全测试,可能存在错误。